From 781cbd19f02c42795bdc3adc2fda287d95f91500 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Mon, 8 May 2017 19:04:22 -0500 Subject: [PATCH 001/912] panic if err is nil on c.Error A panic here provides a more informative stack trace than the panic which would otherwise occur while errors are being collected. --- context.go | 4 ++++ context_test.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/context.go b/context.go index 7b57150..dd0dc5d 100644 --- a/context.go +++ b/context.go @@ -144,7 +144,11 @@ func (c *Context) AbortWithError(code int, err error) *Error { // It's a good idea to call Error for each error that occurred during the resolution of a request. // A middleware can be used to collect all the errors // and push them to a database together, print a log, or append it in the HTTP response. +// Error will panic if err is nil. func (c *Context) Error(err error) *Error { + if err == nil { + panic("err is nil") + } var parsedError *Error switch err.(type) { case *Error: diff --git a/context_test.go b/context_test.go index 0feeca8..b4c1f08 100644 --- a/context_test.go +++ b/context_test.go @@ -852,6 +852,13 @@ func TestContextError(t *testing.T) { assert.Equal(t, c.Errors[1].Type, ErrorTypePublic) assert.Equal(t, c.Errors.Last(), c.Errors[1]) + + defer func() { + if recover() == nil { + t.Error("didn't panic") + } + }() + c.Error(nil) } func TestContextTypedError(t *testing.T) { From e38c36ee0d6d2131b263b571a8a7e2e8e2a708dc Mon Sep 17 00:00:00 2001 From: Thomas Boerger Date: Fri, 30 Jun 2017 21:22:40 +0200 Subject: [PATCH 002/912] Made funMaps for the templates configurable and hot-reloadable --- gin.go | 18 ++++++++++++------ render/html.go | 14 +++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/gin.go b/gin.go index 992d2f1..c4118a4 100644 --- a/gin.go +++ b/gin.go @@ -47,6 +47,7 @@ type ( RouterGroup delims render.Delims HTMLRender render.HTMLRender + FuncMap template.FuncMap allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain @@ -112,6 +113,7 @@ func New() *Engine { basePath: "/", root: true, }, + FuncMap: template.FuncMap{}, RedirectTrailingSlash: true, RedirectFixedPath: false, HandleMethodNotAllowed: false, @@ -147,19 +149,19 @@ func (engine *Engine) Delims(left, right string) *Engine { func (engine *Engine) LoadHTMLGlob(pattern string) { if IsDebugging() { - debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).ParseGlob(pattern))) - engine.HTMLRender = render.HTMLDebug{Glob: pattern, Delims: engine.delims} + debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern))) + engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} } else { - templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).ParseGlob(pattern)) + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)) engine.SetHTMLTemplate(templ) } } func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { - engine.HTMLRender = render.HTMLDebug{Files: files, Delims: engine.delims} + engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} } else { - templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).ParseFiles(files...)) + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) engine.SetHTMLTemplate(templ) } } @@ -169,7 +171,11 @@ func (engine *Engine) SetHTMLTemplate(templ *template.Template) { debugPrintWARNINGSetHTMLTemplate() } - engine.HTMLRender = render.HTMLProduction{Template: templ} + engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)} +} + +func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { + engine.FuncMap = funcMap } // NoRoute adds handlers for NoRoute. It return a 404 code by default. diff --git a/render/html.go b/render/html.go index 3ea5e08..cf91219 100644 --- a/render/html.go +++ b/render/html.go @@ -25,9 +25,10 @@ type ( } HTMLDebug struct { - Files []string - Glob string - Delims Delims + Files []string + Glob string + Delims Delims + FuncMap template.FuncMap } HTML struct { @@ -55,11 +56,14 @@ func (r HTMLDebug) Instance(name string, data interface{}) Render { } } func (r HTMLDebug) loadTemplate() *template.Template { + if r.FuncMap == nil { + r.FuncMap = template.FuncMap{} + } if len(r.Files) > 0 { - return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).ParseFiles(r.Files...)) + return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...)) } if len(r.Glob) > 0 { - return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).ParseGlob(r.Glob)) + return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob)) } panic("the HTML debug render was created without files or glob pattern") } From a40699e07f71b33c3c4a064af3468c09d333688c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 2 Jul 2017 12:38:05 +0800 Subject: [PATCH 003/912] add FuncMap testing Signed-off-by: Bo-Yi Wu --- fixtures/basic/raw.tmpl | 1 + gin_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 fixtures/basic/raw.tmpl diff --git a/fixtures/basic/raw.tmpl b/fixtures/basic/raw.tmpl new file mode 100644 index 0000000..8bc7570 --- /dev/null +++ b/fixtures/basic/raw.tmpl @@ -0,0 +1 @@ +Date: {[{.now | formatAsDate}]} diff --git a/gin_test.go b/gin_test.go index 3ab4697..3cd134c 100644 --- a/gin_test.go +++ b/gin_test.go @@ -6,6 +6,7 @@ package gin import ( "fmt" + "html/template" "io/ioutil" "net/http" "reflect" @@ -15,6 +16,11 @@ import ( "github.com/stretchr/testify/assert" ) +func formatAsDate(t time.Time) string { + year, month, day := t.Date() + return fmt.Sprintf("%d/%02d/%02d", year, month, day) +} + func setupHTMLFiles(t *testing.T) func() { go func() { router := New() @@ -32,12 +38,21 @@ func setupHTMLFiles(t *testing.T) func() { func setupHTMLGlob(t *testing.T) func() { go func() { + SetMode(DebugMode) router := New() router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) router.LoadHTMLGlob("./fixtures/basic/*") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) router.Run(":8888") }() t.Log("waiting 1 second for server startup") @@ -59,6 +74,20 @@ func TestLoadHTMLGlob(t *testing.T) { td() } +func TestLoadHTMLFromFuncMap(t *testing.T) { + time.Now() + td := setupHTMLGlob(t) + res, err := http.Get("http://127.0.0.1:8888/raw") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + + td() +} + // func (engine *Engine) LoadHTMLFiles(files ...string) { // func (engine *Engine) RunTLS(addr string, cert string, key string) error { From 6d071c1d360141168de629c4b686175df2efa61f Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 2 Jul 2017 13:06:43 +0800 Subject: [PATCH 004/912] Add load html file and func map. Signed-off-by: Bo-Yi Wu --- gin_test.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/gin_test.go b/gin_test.go index 3cd134c..bdf5a9a 100644 --- a/gin_test.go +++ b/gin_test.go @@ -23,12 +23,21 @@ func formatAsDate(t time.Time) string { func setupHTMLFiles(t *testing.T) func() { go func() { + SetMode(TestMode) router := New() router.Delims("{[{", "}]}") - router.LoadHTMLFiles("./fixtures/basic/hello.tmpl") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + router.LoadHTMLFiles("./fixtures/basic/hello.tmpl", "./fixtures/basic/raw.tmpl") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) router.Run(":8888") }() t.Log("waiting 1 second for server startup") @@ -74,7 +83,7 @@ func TestLoadHTMLGlob(t *testing.T) { td() } -func TestLoadHTMLFromFuncMap(t *testing.T) { +func TestLoadHTMLGlobFromFuncMap(t *testing.T) { time.Now() td := setupHTMLGlob(t) res, err := http.Get("http://127.0.0.1:8888/raw") @@ -129,6 +138,20 @@ func TestLoadHTMLFiles(t *testing.T) { td() } +func TestLoadHTMLFilesFuncMap(t *testing.T) { + time.Now() + td := setupHTMLFiles(t) + res, err := http.Get("http://127.0.0.1:8888/raw") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + + td() +} + func TestLoadHTMLReleaseMode(t *testing.T) { } From bea012a17ffee4cc1f16352cf567e7791f226530 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 10:55:39 +0200 Subject: [PATCH 005/912] docs(changelog): add #962 template func maps --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aba6417..ee485ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [NEW] Improve README examples and add extra at examples folder - [NEW] Improved support with App Engine - [NEW] Add custom template delimiters, see #860 +- [NEW] Add Template Func Maps, see #962 - [NEW] Add \*context.Handler(), see #928 - [NEW] Add \*context.GetRawData() - [NEW] Add \*context.GetHeader() (request) @@ -14,6 +15,7 @@ - [NEW] Add \*context.Keys type cast helpers - [NEW] Add \*context.ShouldBindWith() - [NEW] Add \*context.MustBindWith() +- [NEW] Add \*engine.SetFuncMap() - [DEPRECATE] On next release: \*context.BindWith(), see #855 - [FIX] Refactor render - [FIX] Reworked tests From 2535b46bab759cf0a43a7e0b9c6e6621e89eca4b Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 11:07:22 +0200 Subject: [PATCH 006/912] docs(readme): add template func maps example copied from gin_test.go @appleboy commit --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index 1276c2e..029606b 100644 --- a/README.md +++ b/README.md @@ -628,6 +628,46 @@ You may use custom delims r.LoadHTMLGlob("/path/to/templates")) ``` +#### Add custom template funcs + +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), + }) + }) + + ... +``` + +raw.tmpl + +```html +Date: {[{.now | formatAsDate}]} +``` + +Result: +``` +Date: 2017/07/01 +``` + ### Multitemplate Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. From a8ea89ea385b2e11f0147006147649d432919121 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 11:17:42 +0200 Subject: [PATCH 007/912] fix(test): remove wildcard loadtemplate --- debug_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug_test.go b/debug_test.go index a402ba7..c30081c 100644 --- a/debug_test.go +++ b/debug_test.go @@ -72,7 +72,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { setup(&w) defer teardown() - templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/*")) + templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl")) debugPrintLoadTemplate(templ) assert.Equal(t, w.String(), "[GIN-debug] Loaded HTML Templates (2): \n\t- \n\t- hello.tmpl\n\n") } From 0cbf0f48aaa3573141922d9dbdc57a15ea3aa626 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 11:58:19 +0200 Subject: [PATCH 008/912] docs(readme): update contribution guide - change new prs base branch from develop to master --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 029606b..76ec40d 100644 --- a/README.md +++ b/README.md @@ -963,7 +963,7 @@ func main() { - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. - With pull requests: - - Open your pull request against develop + - Open your pull request against master - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. From 55dd6364cbdf2a4db3c2edbabc5377a708d5d282 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 12:23:54 +0200 Subject: [PATCH 009/912] docs(readme): update to color logo --- README.md | 2 +- logo.jpg | Bin 12200 -> 0 bytes logo.png | Bin 0 -> 16831 bytes 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 logo.jpg create mode 100644 logo.png diff --git a/README.md b/README.md index 76ec40d..e35c2f9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Gin Web Framework - + [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) diff --git a/logo.jpg b/logo.jpg deleted file mode 100644 index bb51852e49aa24ab09ff7826b1f6c3dfdc99b16f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12200 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 Date: Sun, 2 Jul 2017 12:27:38 +0200 Subject: [PATCH 010/912] docs(readme): make logo smaller as before --- logo.png | Bin 16831 -> 21516 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/logo.png b/logo.png index c58c79864852b50763c2aab9106dd9ebd2290cf9..5f4888c4ba851dbfbae9dd1eb77da5e153b39048 100644 GIT binary patch literal 21516 zcmV+YKmxysP)!4L@aaC~vZp8_rxNy%}#f6IOm4v-z z@1eHe_nq&a8}dj(0zm_U=Kg-?m;LhIz5jRa*{4Yp+4!bM(9I!EtZSD47j?t-|HALj zj*j#@Fqy;QsL5tVHtKZb#d@`j^BXZF;%X1zT(h9Wx^4wg!4KZYu>ve)Gb9^zGKvaP zw&lmq`%ui$gRtBE8SrtgSpwU~0)j60g~%Z9d9s<34XKWZh=}fKp);X0x+9bkDrOsi z-+5Cm_^x-O0L15^m%9VocUh2Z#$-bppli!wu^jPveuOcU6X5YEKT;2(A6P^9WeWy; zZ!h)$7U6=yx2I$m=4SMk&7f?2@<>Wb8iJ3k@V|Y__<+YFd@s?jD@5G1rQl0W2!n_> z$HCvz7M^OR|0fE3`~XZ3uz<)b77+brH~ioK{U7}M z@jV3I_yIUKdO{rgmb+|5W#f}ZL6n(D;0XgT`o1MZ`E-M*w`bsg|M!34!~0SQy)zZq zH~WLXzyERBOv*-$m0?lo@>wm5@N+ z=~}WGmJP8ZCMIS+KE}Nn(-V!#wEl^xHU^Q)3PBj0s{l(%a zYUX-n)YQ@ua>6=O@MVV>R1ZEvVOM*>>%X_Hk;RwofMQOVr zEAf~}@I}2@L(M8^Tuu)%gUpaF;Op6~Qt=Q2K$edsJi9T|Qa1bF`1tVQgE8-UR@8)a zevd|WSPv*I&WH5KIaR^O(TAd{lLgAOYLukMSE7@v%Ln9#}p>1?@^eEoz=(negiaEvD(T6up|~&^ z@*<6^XukNG9eN(}qI!zeit+uhyWMNF{XO)wJ1<+2Z)hYYCbr{$ofP#lxMh66 zN?>_Fa$*qSfF=^T*My^7GD7<^z>Ioq3+!v%s+IrVUL2DM0L^5p^K~m2x)pZ4RE7(N zE)xJyZbVP`@SzNHqWVef+vTSM&OnF?y1;DV1(Es?dDXI7(@WGfYlvnCua~XRS3F{3 zI6nsl25Ln_g!|)bR?tbuQlTv~gS`y$(#`@uHs=@m6(m6^ksEpM`(Nu` zE%@-W=hx;#e0+Q>*(!a710zcE^Yh1(?M*I@47%k8E(T%ng}>SWlDOA|=}Ep&%rdI--RNgMA?m6{wRXQKA{+YqUHV?OT3^vfPfuSO930$> zY`gvc`RAW$iHV8RirAK)4ZMR7k>|JHKHKM9Dgp2^Lb^j* z;!8*i9xMR9B6ep;WL<{*C{xB3$PYezEROC5+35jP2|@6svwp8~{PaK*TGy|pT#xA6 z1O5{ZfRK$wFk)h2eyP}YX`U>b%^s8=*;yj+Wyd-~YK$l0e1b-2^k9^-tIulVCKVY$ zX+Z=xv_AfnhzRJ8z~&{GR|7uQ_1+NrczXxgYBr$p>XqNs^z?KUQJ+OdMtX;Zg^lFB zj*5y5FDWT$L%HFrev)akoC=_k=W&6vA(Qhn)Ycds`eXs+eyP!iM92AEt2xUJ4bEw? z&sWP<{1b)!{QP{2?Ck7O$;k;TA|fKzJ$m$Lox8jHy3Lz6ulwnzpVkc?Jb0b1uI@VW zbxLd2tS())Y#ETxVA`~4@csASL$6-Fz|hbTR8>_m?v0X}nOV4q|7#|s>CVI;0$!DX zc>iG{{IPnKTPERZ1?(P>8aDe~a_E9x@$s>{@ON2JKfEoirN<*X^$z4@_(5K{p2+V+ zKj{upK9)7geId_xH=I~MvVa|L-MVGBe*Jp8DO0A{4Ie(-&eqn}&dSP)q29fF+gVy# z+L@S`*cliY*tKid&Q43KwVjfZlAWBK96GKk_3PJf@`VfM;pEAauz&x4*tv5jY}&L5 ze*5h=Kma&6IKZq~vtZJsNfZPF1`MD;=-Ra_kmG`;mKG&-4Gn1Bx;3{{ zzu>!k`Ev6!XU-^*1y*|a@Zr>R=gz^xg$u#7b7!e6TGOUYDInzK<&plEyjM;@Sx(+B zZ`Q2Y2YkOKz>x0)?|XQ>x&T*ADk>^KwpF?f61fScPoECB_;z-7 zyZiU=@34LQb_XKsIEWL>Yw4a+ym;}V!@z+9H$Hjtq;pN*fBEvI!`7`^9s2g|>p)&Q zj2}P#poN9S2D0rt5I{S$Z{OZQO-;>#a83F-nTL(!=J@#MpPS(K-+za-Yu8f3?f;KI zHo}r6OP&)h>o920AcsST4mrGe^QK$Prk3~tNvwklaw5ztG*Dw;g?Kt3hyCW4kJKF<7@$r-zaxUm5UO#36z!QGl2!72z|d9afcx_ zz`P0obHh79@P)258k{42|2QPI2^unF$ls!XhTM;E1q56K;bP(b{{G=IIMr_W`1rhy zi@a%)9#UCvrGV8L;yHJF<#5KGtf{_oa=^eE%_>}PMn}kJTSIBdJ8l`=4<87Xz`q|$ z;opD%rSJXwu^7^mp2LSyHci1yU|)sgkm-;c(F2m-4uc@?Zq=JxD(L0ywUWuIk&%&y z4j(>THhRpMvN?0+l-;>==R5w-6B83+P*CR=+RKx$TYE{gG2}({ zg6zx~D1GNfkB8=*5{^E^KIsp+If;-NeWqp)2OnKO(p^0Gh-}imQzw&WZQ8V{!p&N? zY{~qP$Y5GV#>O9J%$R{@jr&Aalf~;x&!$Lid;xx*&oH!!2fmz$Zt;7AU5(On`IA~2`2=C2q2+8*j=Kv zUG(q%)2nviL*p-+1B4B(XxVSxym{vM@#E9(-@hL$gGSwt{QUe@1qB7I@p%I0uA^j% zf(I5kY0|}wW;FyaWj&iQEe3SYqO(9 zKuTgTWTb{daZHa|Cy9##c-Z4X1bF{a@FfJ#5eq(ipMAGW6`%&ReG|AWJcniUllWyAko*oztA)p4SnxYdN)rBM?|eDpI&k2?kL2({S`oSdBYGC0&H*JGa%_5K^*L(}p(185*84-jeLlc2mb58jtXLww{>YHTj$+aAk4 z5olC)Wa*RRSwmsmc!+1;C4hSm#YOKRKjSXsMVVJyI_+JM9{BmR2Rj=*$s>N0m6e@^ z-z3V%#{&lr$f}Ch!$2Q%Zf3^vz7Z0r`<#YXn=F zP?-!~^h#O9V=-GFO4!Db9^4z=Mb3mE-?fmImI@y~et^8(bjV442<+GM1&&cJbi5)K z*Vx*}+ka@=wr$w{{rkgY5UHDyoSbaLVzI2LW*aaytB3#s zWwpG_stxthQo^s~MpzY=aP%bSXym)EuG>Of=oa9Fo`vEZU!t*wL3uF?iVG6q^{eOb z&}SLs#>|Dnn6Xg6v4i|5YsMo|Dn|KucGVL4+q40F9Yq?A)8td5d5DJ9yCyb?CKn80 z8a;Y+-l)-|-MziN-9tk|WwkdxbCi~rcBk@Q*s^qi>QC9v1&sJP$zG@OBRf|C1J^BP z1i7qX5a&M^QrLT;B=;E<1?mQG_T!6A1Zzy_q z5=t_TL*(lf5c_H<0jFdj;{v%8_}?~xgs}~rY5MVsez(m?O1HPcJ08*%8CGU46ItU3Wy8| zw{G1mC!bk#?AXzwU%!49_V)G`YuB!|`0KB~ERG&MYH{)6MT=|Ku37l{KD6-j^RoyG z3$q~Gl0|xYdgs*C)UPHu0RR-qwK?H+7P3tF+RGb2{A(+LfYKcHym7poj8ZXy1N_g+ z_>ICsa$gDA(z8SKA?0;P;5_XB0r&L4^FSAvKUD|Jj8*6wH>qU+tddf5Xx&l)blNJx zyvZHl@J2(pf3!2aIMYSY*UL33R|VggF=H^W4-;74^DY|0-Z2{G*I$2CwzaiY#^@CC zwsLrQc-gUI$6(&PxpXUTOF)LX4*XFqO(Y0;#}h}uqbG$IzK(HzNH_^d=;}iv&;J+0 z;mL2ykjU@K$W2jZXk=7I0A4nD@ZhpZ_V#597cMOOZh zo*h69#qR=*;q_CODy~Z>C#Pnc{#Xq`_qsqvkRfD;8bM~TA*8-Fgm~-->1zT}kBuPi zg(>7m7!uCeiD;gNko?97BL6Xh>!$~U<6;ZwX3+_h6q|ygLQ{~J!}+frCu`B71!!of zgH5+quwm(7cztIuIhT%*6Rrz+5g3fxk$O^C;;NOB_e5Z%Ns|i~E@<(%TBlB(BHp}t zv*=SMm`tu2IhGxnSeo>Mi9MLmgZUbSv%#J{dw@(lEMB}A#*G^XHa0e3V4zPUn48pL zh&qB@nDBae#7hVgycxKJHvyg>M)KfIhC6|lmKJDhBWYKV7JVN-$6W$%82l|B2ak#W zQC&_*JMGrYx$ymO1S-1f>gocVhIc&PG5JYOuDL}2Cro$^cD>udve~+@V^v2uvrh-E zoiv0O*Nq5=B-}ErE7_8{)efpNFk?I*`=+H>@R=DaRRtdo8=Jn&LZOT8Pba73`reKb z65<>QXK?iK@o_wP@}#4)v$G@FvK{BopYKS-0mtFPhdcJ|+t<<3($dkuz`&6#xFg|| zj^w9~KmGJm6lSN*n>P<8Po9j*5eynM2zvMK4Hgy_U}R)OMFH(LZ3%cPfIG!;QIkxVg6)Ya9X1eZd*FSsXf|o?L-%Na@h#jgfr$w zbgfl^p<d|S3`smPw%CkvNqLVH>$ zvDs{&XV0Gb5INL`$gDn_Hf{1*v}lpfm~rELdiCn%Lu6MUve;PmOMu8{<al=}`GKVhUzS9hzUF!##A?A=5Zb-we(7`V( z_X(HC{ghI_2A@AQ@C-WFZf-w z1opE&P{8Ucpgfd{Q6|cSfK?_1e{M~b5!#!&+c2L40hkl2&lDnJ-|Qw9d@E+R7Y)8? z)28X*_CsVe8Tjg8yneF13MVS7N5DvvaGPcYpJH-(3!^$!B#je=<9ZLVAyl)*X^RHm zjT<*~P-sSNQw9~Om{SyZ&x4+=4wqJxNrM0%8p5M(DDc=}_n8|g;guCv+pR75FtM79 zVtn)L*|WQ3@cF!vmX=1NkvVVXg_lYgGl!ALjfE4I#rCA&D@t6q+76VXQil<$HqpuDV*a-W#66HvtI#;vy^&IZRaf}E&9kQzK3a#$mYLSt4f zXvLj(X@zVTA0G?RVJ8``m*H<&LmN~}0q}L~s3!m;7WTJIh)Zq%gc))HsTEAg;(t;naAo6#f2#X1%E!gFM>4AUt-TGHD~}N$6h7> z*}clS=0pvI_vM9<&$42`_tvLhjiu4}!MA+*@~RkX)v6V*#!7=4DJdyy!o$Pe-@JL_ z{^-#o_v_cMyW`lhWsCdIKmY9hguiBiRSJ;cD6&zvK{+j@tFs( z;C^(e$)!YhVVdAIljKmK3EzDtjN1AFTxe zL%3a5^kT@0cY}nm?T`@R3_0J+S>*Von-Ft9^|fIWQp&|<}k6&C91 z>K3O@pDxGBoPGNAf!2KfN`Aj(GX+IxuG|8YTWf*5wl1jXnS#lHVbE^EV(7Be89M!W z9=Z`Q^*$F?r(lwl%q}H>(~}s;%}s;EfRW6CpiIQ^UraexK6?ODvZ9#X8FC}KK_T0W zk0Ls22hlrK)GK*)(a>R;(in;Q8S>dte~=UQ1T_nV$X!+uz8eY z7UV`iX-PH(B?9VQ!gaEx51<7d%c=>pN{c;76^l651Fo$1dI*sh&6#tLdud%`?=?~2 z+qiLKRq$C^SyAv+gxA!1L`O$`N4CN;^7}Hh8H%cWfc|4eC1p@<+Y$QBSq_6wJOdL? zHkh1?1{1GnB0I%`$%Xh&-U9239>-t}yCPcYotIccx1pfqGua-? zXoHpi{w;^(;PI8ARE04VFwvohMS>5H5$k75!G{$6cAm)ZLXD~z$T@eqN}MFVYAzRih%J#F z$cg;KlsdDvwl1#;Y`1RR(i$~tly9d_ow70VL)bCjQbQYzN6&;#KkopOt(Tz7kr&X3 z0L}D5T!ROfIO^pU2GJRLkev|)nV~%d{ep=|5YI}+SXe+b!M~w6 z##Vp}B!$i+T4y)s?sp~YzJ4COXCng8rEnF%t`nQ zax>Xb!V;g$fi?0n;}6pRLnS6R?KWgb^c2ZebHmLbH+DH>roDxCNe^h0i)3Ak^dZEj zw`5$85X;9BR8_c)3FASweKO(CY%$YOC-pk9&e*5v<#LQ~21#9in#qDcJr-+L&` zk0l(=is5|u(Nikg6Bxq#(rgOI!qnZ8hr6T9#i^;=P)7)RG71XwV!5W6vSP?ga3xx7 zAF)9SMI1|7?Nk!*rM>D>ZSikma^Gyih+ITj6yJx#+`>Y#GPbSMq5J-SzG~2Qx|j&I zS7RYQBbUf(|3XgcWdRPtP2%oLw^EenvN=ChXoUt)T#!V-_YvZW3|P!I<`&sWk0j5z zdAM2OR^;=4={Ai(Ev-%66bZju%rS-dsIz2iEoC&~)ab*I7uj7P?5bQSLPp&GQSe9V z*RC8IA?H;FOqkf%DO1FQ4}&pl0-mBek>%V2zHZx7Si|PLN;-?sVF^qj11P#4A;qsnnl0!E;YPPU1VHt*@I}Vo{a4I z;e82|6~{wT)Gn%sR0rVLSHl@ji^ocQZc%ISZCIc~af6ZB9v&Xz!MAed3e4van^1!2 zHQjUXs|Vh!d+|_4v^?ZkX;G`8h%ICsW_O0{G+)B?K0s3VDzc60b6bE$SwKO44$&(8 z$oAZ|qLNfs!a3hV0lQnJha%pS0wI;PUMeNbBx(GY6=4IJaXTR=D}j+g-xDC_Wk*9% z&NIkP_JZh;1CW_`4$2CHXf2rHyr4?(5m6!NdS8h_Ck#Fe*$)j36%Rf&HMMnEu>0uI zBO<|Px_r;q&+&XBsjyyNmJb>1U#bY|)T9!1g6mw5|3jb>d~W=Dcwe4RxQz`1wCqSb zNQ{2Sm}D^GI6FI=+g>1gaGis%N;o{*glNK3p(K4bq{Lixi-@9- z1Uak`0>EJGZlU`q@RGte34C4)L~le1F-r4irB#X3R*d!BEUv-I?1NsELY- zN|->VQ=j2qwQ^y8o(1_OWl&U*PB@ok72qM~N{ZM(xkOCB!m9O&)7>FE<(g2_J$*=t zzbl|wmL&f|xn5XkV55lN!*hOk#`Kf89{$E*q87!^g9i`dYl^~^*Vosl;5&QvtN{2% zE!*(bg0G)fI20BaLs4D?k=x8gh7*OoSVe)C7_t zN=qR-g9zSi3z4P3k|S0@aZxsK-Yy{;VpSP1HRd#AbAA*6EpD}8fj%@LsFD-vL2gzI zx5R9ejZ~Bg^wAcut!O}lH04K3l?=IK1>u)#r`-yJ&$hd&X2U$&NjIpVDwH>|zz9b|{~luB{Pq*ulecBk*B0gjJed6-eRYlZ$$^B`Ze zGdDM<;OpDBue-3uYdLcTm>hf#re`C-^c)*HdvTzXcYLFg?|NU2hdZIM@bAALY4nCD z_Y)S^j(Y1v!15mQ(>z2PVNnpz%ZY^oP9LH74WTHX4FxG~0>9(kpb>vy%jw3 zNI~>K;Pv6C4lJqRGl>F2xOd`#PgX#R2&c;pC7`=y3lWzsK6#ss^yyBh2Yp;MvklX% zQ6lhR&szlrh5y6CJy;S=Y#9nUc|r=5)HF1~e3>(J@?tl5@J+qJf%hM|1+L#k%@#27 zGJR&l3?L_)Tl2UuCxklQMS(9l;&*uU>Xm@Gh1&LVUsL(8Al6D-j1MAE#*1hRg%Q{zh5qt=UY}$q{z=_76Fbq?IgtZ zu@G6vTO?J0F5(K0EW!4OCqr7g`V=d}h7FSpd`FHPvBMu2b?IUmA0Ka8(RGNHl+f6C z85DoN3>pr9fr{+}XxdybY^o{N3XxZY7Hv8}#|aJ%3Unhc1;dNz6zbB-&rPCv4W%L? zM6#n76OA{Gf+Q;|lQ9kpFML|q6nd=m@F~paC?}<|_fn3Ezh}LiP~qy*BV5UyhOqNo zUDV~4@(>Cljj8}EJG3+8WpgNpOpiM)HtZ=mpdX~hdJ*6i((j_txU?u9{w?u?g3RlX z#@PoMv96Gpe1K9y_zuWTJ`U*#H|R}ORPYXblk(xvc?URrZmz_%@az`&?$}v|dLOv} zeU5m8&C!dL@HxJxVqivbL39TniNF^Z7uOLh6Rlgfjz+a$ai8V{WZFZg5T&Cx$Zxm? zP29pLsqPN~<>l_6Hf0@jFg0h2)Tod836!1gGT%dLaU=pvJUO6uE)GmC)=x;4nRhHW z+zf}u2|3hb!R?xO01DY%gfhj*miVGS-yAtP!&+2)O@l9C-Y6biGRGyQH$inG8k z>KHta+5^ubT;P6y8~hV-9&QE)!1Lq+c>S(aBDx)&3NC&Tg#2K_m50#J^B$Kjgb4sg z8idC)T)qY5R9jbs==JiEI52d^%$c-fB-X~8JbCgV6dKiB{6WDb0JL^J7627Pt|6eX z{Rx=Nc7&GQ2Y~js@1rhV2tRa^(12U3sQ(*EpIL0mSUBm=Pe``!*;Bq;m zAd5W<-X;50X@r~{A4s@bvDkK@yzlco2*RJ?e|;dJf0LX7{_jd4AhV2;e?}=B3(JO) z*W+Q()woZheqMgi@9a4k;OQxntWVsdTuK_C!{ZD(dJ={^tp^3QN+-qCsZ%8f-}UR) z>5uN-z2!j00$M`tcXzR1`dn&riX)afUnI)R#RTYj^aZqE;tIxn2MYwQVyO?|Dw_CS zdU|@r;u3C``icOk2RF|&LLcH;Hy|iA8?MI|!1cJQ(Xjm9Yf5%r&!N9}C=9$54R%*z1we$a`Dwt#Na*YN3i^B9gI=fp zroXeh5+m@NzMjtrz&seBeRr;sc<*&PbU~`%TYl;f7;o1K1`HTLc0%rYY~8w5a`2r! zd)64Y&~DwjIRpjFY1A&6C`?Q@DYzWz&n!RVm4>*S9@MI9?vSz>$JC65b;w#kMr;y;hOlHK4)K7f8A$2p{tv> zROLP61uMbQgu4@!mX-joZEj4AV27n@2lj^O*s)_K7XHT9gl&RCiSxA ze&<8!;#YUqQ$P@byZ?o77uj{)!-rGfuAiUZ2~2^+=bEms8!YhTi4xPpBa(n1jBo&Nk#Y|Il+;hArw0%kjJ{wPeD*N^GxFAx_ zvpsVTdK|ha74Y$ZXLj%!GtgKG_^wUok$Vxl! zxz_-J7YZGZhjJI`d`OMkC4ScH)Ez!~>J1~5e+uv@_&oi%$MOyq3wYZzgd1Mu(C_s= z^-wZydXX!S_B;2GURTW`zyOI$Ezf(V&2*av&6Xor~mZe`Nc#JySFH!bL$2RmoNE ze=_J8S?m03?y)?cR3y zIxJn~LVcgFfGDOsX$=KmuU@?<=R>Q+k3atSY2Y(7G<-I2;6VE0UOju3wOX*F!2qx2 zp)k6Tyany&aYZ7apq9w55%xa)`QYny<~f(+cs>@*!H6t5@G_fz=R3m9xSZCbX3j@$ zl)hd8%r*8u|B7h4SLxPna~d_@>*PPL5wJa^TRNQwauf1%yS{|mNlU)t-)gm7o@X>QU?Mb~5k{Fbc>)Cq# zU8z)@xI}>>P358|*|t?&M9cpAzn`Iin>lkP+`oUHK3~21*G~hVzrVi|+WX?-;!Nwafy(s(^6*w8(N zG8h%;&%TrheE7S5p0~l~%nd<~NaQh8WDp4a$FVtaThMcIA@H+hOooG>J_kJr$gB@t zkg75vOlAjsK*24b#0FgsCn-$2+#^{u|K5HMDkC2)$0w zv!^xF?Cy(rn9KF@t5+c|FO z2oPbg_Ttklk4WdbyNL}hw5o;cYv{cHx(s|0 zg3rk9HZ*eylKMI{oxYg?-`KHZqz}HM5xMpaF`%?5(a>}b`qDdE213#1rtSx?f|~2g zx{EfNRmu5Qu3Ra7@Ev*;XRomLO@obUZsGa{Ox&)^B}~+a5P)@bQw_krP@U`nJH=Y5xt7cYXU=HoMwT>yh(C)$o=s z_=1Cjotc__OZPW?@bT_U!lhff`GN8NJ71+%jMrpLl!%tDF9_%Y>zvaG%i7p8PZ)e! z?b=Bfe5X#GV!(%4N=@tWBBd6bKo&>S)fYM)xY=M_Tc6^m?t1VVwBP>_TDd)^M)xnd zyU2+EUzaXjqzk^)t5-V~4oH5#veiMN zp&q(OWH%o$Id~KF5B*KU*qggGEO}dh{DLarv$eIAF8CHMT<9#oT6aBe)Zi0W384+) zjGYf$rwU7V0!XVv-c2df9m>s-MebApZvZY9{# zcGRd*(goiifBYc;KIO&xzdG;grCAeN?HK_-CtT`?KzUkAaOBZ}M zZrl(6U+Xc8zHaczsCGu?VKW(Q{QB#!(gj~^Z1kV};A`L2S~lr2s?4$3e3!w-fddDm zZ|4(kQ{S3w9kPj+5t7_4KL#7uu3eKp_$rFORBpXX56dQAMqD%21|J3&Z{NO^KKNFw zSRnwu786&>CSFFMwC!(N44#LJl#~=5se^CCh!MZ?gHNI7_p-^C5vZ8k3W#HMA@4QO z)z`N_cI+6OIdg_qpjTB>gXXS5vIY2t360)Z5{(BwtZ-H*;2Sbzi2ai%PiWf!%+Evs zw%Gniwg}%aDQ|rsAd2k9@Zsp)r;lLoi+1|5MfjFI8(n${;9>LT z&2CBuAv7I+JuX{}ZxMVh!Jwe2 zBY*{hkGcb&mX?+de;X>=@UQ0(*JEHPPEILF?hOK+$3VD4SS;f`J7kWutB&*VzVI^c)T?rfmSVZ4VoiT@_>b z_#6FIxYp_pe8IuNI=uEq{QY(nw*D;xQ*F`>|GWb&#{68Rd0};gcGb3R+k)nVU!dh$ z&o7?JfZ&r?Q587Hu&|K7n$+b`R#r|Go*0$YXB)(#D(v+K<@Hx+5_9|E(?Pvke`s%F zhIs{`YSD263f)ZaXJ079o9(NT(Lqpn7 zrF-{oU}k0py1Kg5iy$oSX!RNjt=3hFt!!|8>V%-y1HUO zBP1myRq61}Pq>KK;QG7oz7vqE6ub5ZMW?&c3ptv&N`ND8+O!GM($XXXn>gZeL?0g? zU=+Zn%|LU-v3dm`zbK%osR{Gu&8vIxp-l3}AAf+Cmse#{(bA*!cH#0npM$!g8D;fW zR#vcl`7&U!qL{5qQZB-4ZNHD)NGNMDTZoDpv{|&9aMsYz+G>?1J2Jr2*4BonPoGMB zev+O)o6V*&B5&){UR^)E@>;q2SJ`E-XLFr{k0>2ivEfHZNC@-i?Ita)H_N)I-58iU zbt;*779$gV&Z6RP%gf6FJvi7?f)VN`e(E)aLMum4Mw@Ndt{o&NCrbr%q9{8%n=U>N zfGxJ%sq=N{bL^1-_>7ElEUo!azyP`-e0#$hsC*QDE;_foh>4dkV{qOtTZKC@Sa*IK}56d zZpSQ&*ST|2+iGh9;F&Y0E4RTZb(`jZVu=vvD~ybcRA^EkqeTl<2n!3Nnj_~}k!f>NiR5FQ?0=ZnZA{4XsnuKNhLPsR0DC5!Y}a)_CaVZ(+|ZMMG783BkfmsAE< z*M_Dpb!UR3kKG4SStctht6mop?Qcw# zbyKUF7n8`N+TCoK?G|NXew!UqSy>t6=H^lvQ<_^b@A!FndCX1IVa1_31E1}rIRfBQ zQ&X!y@GV)gq;TVg4cO|alGXRHmHI`t+u%))J!#UUI^S-E7ZbUbo}M0aeHy>GRezH> z-R55ms(n|2u9S9WBqpqrwYFZBx0AHIn+VuxMB4DmV8(Cmr zpn#QI$t^&77wHlV@><-qJWo&0dXb%k0f^kT!g>#iW__!-J&u^o%*Bo!I~Y)*45_cL zFVYY`V88$e#mHRXI!2BfRe#`n_UzdV)UHmPI3bu!zWTKE zWW8N4K|x6wG&D2`hbR}A>9k54_9^9=|K?zzP z6yNw**|aBf6Y?~2+-AFX?_RBoZ{oy>1T_Ci6@dNw_ov5hyU@8#YV)bI7rdFaZ{J>j z;KM=Ly3jTNy$^kPn(iu-ht0Cf*D%G~! zN}cNJ>hyQW^@Kyv#F2%Ch14`-oG0|AP*Fj>O`R0i6ppfKQs0AYFLuI{NW*NTRJpX> z;yv`37cN|&6Pl2aAkjjiwujoJGy#aAO?YfQn;{Z|59QR_MA)EqEiLf*`};FN3{o!y zzW0S-E)O@-rb&HsK08s9Xz=F+=?Jxx*U;l#zI>TZC~m)!F66y?_oNEGJ{6gJ+THt! z-*$D38BG>{^V3g1)e3yrKt)>Md;R)VMI*kJQU{;vhAV<$U3JpzuZBSuSS0w`S@e=h zkY>vtex=8AcXy{zDz%CHMPH*d<-S3KxTb{;z3jwCuo###oB+iH^goL`zkve>!pV~- zsnT@#@L}3K1?_^;0^dLX{KGw$*4olH`u}oBP|k1Lw*J9~!^Fg-s@zAkJ^6i~zx8U* z+sdnUNfzovwP3?a3P+C~CERg*MOF09Qq>-Lp;s*j*o$8j^8o^!7bAbfoiA>Omo8nZ z6F)T`4{um%o5Ut9(k_!Rdz-+u7#kaZ8SwE8npC*uiwJ44{_^Kvq~is8EZp+2NKtj* z%gM=MqFdUwYX`S)-&mJZzNy$a}JyVRkbQPF)Jw3fUUj%%iA z^hWa#&qhh(?n+CkJB|06KPW&N=bE3N&jc1|)j_K=?W z-Kzq=&6_uWIq=~?(?P#}{b*JgPwQ1di@!87!1lZXMRje)dw`m4eS}x>3+`$geQI^i za(mu@yhdBb3UKr0%{mW${GiBT>yn#bz$Z_6(wp^LRqnPgkRh=?&_C}%^LB=e{O9TEQ71M%4C>{{ znQeznqY2%0*3tdfvsJ(s5)$%d!8c$)|4=-UsHjM|f4`D7>y4QswF}pt@~c4oU(~Lp zxtKh`0S^=K>>m?;cp*s|-3LH(7cRi$clbh^K@&k9#apl;a@TK%{d zFMcGIbY17S+ZmaBV)JkKN?hA>$W4P+LA+6z!w`EtN2C1 zH-G+ocRUev8KE}Ellzn~#!ZT$JgP%xFq4gL9R#|xI3RNEhYugpR7bkinpAoRRChfC zCG-9gx%S!^n}NdO{e&w9FkG_TsJVnY$uX&vr%s(@4F=E-sxYAYF(ZZTmV&c|<351p%3~F5mMvf>Qn%nL&*RSCG z3|h3+6{wJaaf#QjU!z_QG!r4AhK*eP#ful@MlPbSU%q^a6#+m?TU&7Pl~h1$$UD1ye=g?zGN04S|I1uYHD1Vhn? ze2j_-Qgw!O=D0-0!@vu9=jZiX32eeD;pWa?9(*{KE?r7}nwU6q{`~n$Cwf<#x~X$V zCLV2S{IyWf0E+3HC|ltcis!Pne9@~7?hSAWoqB4&)xRPpc5vI zUPA@!ZulRq*PR3HX=_2PT}O$Jqe-@OrKuZ1ZpYJl1s#vtkC`iQJ{6HWUw){msV%@e z7qs)4>_! zD?o=$o{_8YI1p;r(g0uw%if^8$4_LUl)~l*(0=KDXg%sDP_-Eiaux$Y&U^r<_8I}I zLuY{oIX`8mJ0SOki$ZGj74+*mIyyEC@J*RAWkH4ZL%9tmO>-+KH3j9(ck5+x^mjd= z3#O>w6%@2PfKp3!>QhBuDwc6Y+nYjrLr^sB4ZVI^N3_GLW{Eaq76?o*f>&A`P|0dI z$h!nK+N!E;%mv#YY}>ZA!GNzh$|562j-=pQyLN4*Y%pndJ-4Yx3Z_Z}&mKn=2u%FO zTvjnkkb(VTCf;tztnXmy>`@ZQU??*Vx9b5L7Egc^n`gomm&Nel=qk9ee+eAiFqPbh zLn|vgOj_1>yG&{d-iWhj&-Q52PzKuH(AbPYlKfSys64jkb%UN*t&NwUnVdXqSTF(} z99Rg?j{OWz3EgpZfHO|h;OP1(aA=)99CEZL&wqpyn`VIbU$fx0>jLmSO1?`7efFvF^3k-AN*xpTR2{5u#?(sk3T52 z(S?=s$59{&07nF{c<%|5IPmd)bDV$}l_Eu5)5gj9gfa&W4GkL(_=xssfhYCiK zZsA{5!1wg&)9DQce13j@7HDq4K3oVsw9E_GyByCo$cfdQxP*!bxAy&1r{F`nb6|0W zhoc#&Y`xcLz$b4tyo#Z_Nkbd$+qW;nz)u7p7QYryI>sz*kQ1vie2wJql=JqaK7dZ9rYm_=~hnsPV4;<0dHf9u10mW?((Au}_B zf)A6v@nmre#=byoZ%71&tc#M`i%PsHNVE|EH5vkfvy!< z{G$D{ZQDUhr`wH6({26x#VXfPr~P$5XJEG3qeqV@_?#WaEv4l|*@=SoiPW7yB7L$Fzbfl?rD63C$`ZW*Qav7VfTcE%WBh zo75n|r>d&z>%40hU5tu51YI$(dw1nUXc%{;$utu%SvwR>zLqh!oz9 z#vdn$oSs+1u(_HB=>2)Q(Yn=Cr*5bse)R0w)3QN=Z|KmWz8Kqw8~}^p@Sl2d!G{e&cqx>kT9KkwsIs}C8(2e`m_&U1@4tVmY^^--ojG$xWTbRK zK>--gT>I6557(B>W;aam@zN=)GdJiib7}CCZZ%>e(~3bjQ3+pH6v?6G&_sa``(kuk z@>in=pZd?Pf@`PSp}a{$9=zg+SZQGW`t?;fAVx^2ZU4JLPkPsNN9nnu+$sz{+=gk{ zS@L$Si z&fF7yP8WmM&4-uM%Ovjf8ODt=Zh(`H=cS<9iY6st1t=_MhG~|Vd?b7$q0+$v9|8{x zKT`Qfg6dWc-p=QAQ{Y+?6XQc0H2Bif)2&dkxP6;jWu4a~5Zhx89x_Cb0~&Xui8Kvv z;$1^Q=lhvVD|ZZ+7w6+e4RF#Vd)ho2JseDO`{wJ79(>v^z70E?MbyBg=nWOd+0C0b zGqv{Ky-TmMh;5BbL2dW*2Fd+eTl5vs)RmVU0d<#GAiqBply^Js=r@?7V~yM zv%j7a4j2Nee?4kgVfK3iKy!{0s9OyJ&ArdWf6pxx6v#EmZ@LXi+a57Fz>S^EPKQ;t zx-c+EZWQ3Nu&}U48yVJbL3s$fc%zn!oDdU?Q2tZyFIeFnqkC0mI(-$3_O+13nJ~UM zK3(5N0lu9(ciN+jz;jLUgm7MNFIF?WqMifb^K&-)#;dJhHN*&9G<_e*Xu9G4Io zAnKXA9DU8-DYJ2a@6DSxL`GKwS65dGwo|80(F?%3%J>?Eb48UFpt!&4_N3zIUB#J= z<;a?~GX#@G+d;|sg$x+=rR{F146+~$rE!1{2i7Gz3x(Y8_UD<@k z&(Cj6qXFOP)2Ds#q@$ywDd5J98%KSmyh7Pirc8!5Q&!c^=(+1Tv|8s0%0xD7-hVQ* zHt7Z$I(pF3tT(i=n+W=&WKnhXKYOem!VeTf7*X<0=v^#=gD^2=e$a(b!j)m?k z_d@fv-q3XYb!fWr7AS9j1PZ(TK;eK)j;wau?LC|cnr&3z3l0v}!Q>%cQV}ns4ST}k z>%_!FkZ-Le<;t3&)V_wtRND6jG=4h|+Oz+JHe(l(i5vkY)_uXs+L}^(>j9wAb1)c> znGHq@w?T^r?C$$g`5W@;+`hra+@hb2X8=GmH8r(#UimaMxnKhqp3;FfKkME$&}5Hn z(Ho?TamRCLs-VQ{Hq^MlhvU+vOR2m(4(`_boVu$p;fAO6>Nr1?Ep`JW+6JP+RcK&f z(D=ZI14FBbn$EOW!+-+pi_6<; zaqGpFsl13^1Y@&(fwBc}NNz>49{f>A*sQQof=^dh_bxWk;}1c{KDNB5V62baV(SCh zqBo=@Dz{((@Y&hfHBRvT{`>D&v0yka<_#l(aVy4Vf%qEx^{QZ>2ic-G9C=Tu-Bp7r z0AAbi`t|FX{PsO047tK|N*);X?Z(L#y`dxxt-k~+x+VhP)6&v%Y0_8+vhBbA`ilYI z`0?Xuk29XP1AE14?|m#={4ZrzaSH{djW?j<#6=>FX?c8X==jgcZ-yS`BFyPx% zQAU^7>K(zUIBTm62wx&!#rXxa>|zZ~sws9985y~)(Spyx!C{@6nkpmr-MxF4aogZ~ z*w;a&gK@p|5tC6>uTWFxm(YC6-=L^tTm^U-4AP{rj~1$`v`qqnkK9R2{zGPFCUv>t z>(+~1WnlPH`5KO%&|IjMeG_hdMf*k%K1?LST5m-~g%BSf&sbKr)k3gOEjo}|008ef?(9BQS`a#k4qbD z{l2e}T5NkzrPTqCHf-3yYVtLVb?esI@v^|4KYu20kz>Y;fnR^+b~dEVQvQBPO&zqB?3aN>n&mGxTm#J!v&lBAD0Uq% z+I;u!-4~mDZNtgQNr9;DJ~}!&lJ}Y&J`1#+u7RAZ%nb8s$?pN6?64QK`i!V?t}(IS z*4B1nldpZ8K7Be36Ul{ZxQHXHbfD5sA3BVl3r0I{L(^?fpsBMTG~XA%jbN4mjcc>R zZksz_fbzNv(BAqxUYUz(31cj$PMw+-8yjoezDAlG9U zwC+9tRP@b2OGg)YRbV9{tddWZx@+_2&$n+P8`Y0(+qOB69XtMsjg1WrIIV7CQHI2d z_cw0dq(!UY4HKvV}H8nM;hL1O0 zb;^LGgvUbH<(M&JI5VbAbN2J|bCx0MGlrX++sO_cI{5bN+4Dnn3JaFyJ$m#gJbd^N zUcP(*5fKrPo}SM1%is|vvg2pBZry_Y`}fn59%ILjfnmdj(QZTN(?kKBzp55$cNvM$p`43?^HbBGw!_G+L z|FX&d`jTt$m7%2`#?6~I%a<-)TCUqsuN*;w3sGGwDl{V?M;K0kfO005?M0wsEy2LR zw~&&OOaXwoCgtVj6tL(DY}Ja(c@eC_{e+uT6uu~W!@~oP z9Xkex4jrN#8aXnSj>CY81q&8XP?}pd^*d>x5%w}e0wEm2WigFdB zET$I{&P+?eq4p@M8h>^Fhg&6z7g*72?AWoigA=))`FJh1wzih@=FPLbapQ(1(MVg$ zR=bf60BD|-mG%Ae=g%Do_i-TH%RyIH7dvJoFOKd zW8iDswrwat5L5`RcI|b**w~mR*WlKQ+bVKIT!a<)yI{(x=UwtghQT7v(D zE#gOy9__Gh-8zSJ=gv6<2M0U6OHKWO+&uDM+noPD)7V2R@nE+600000NkvXXu0mjf DV^9bK literal 16831 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 Date: Sun, 2 Jul 2017 12:29:50 +0200 Subject: [PATCH 011/912] docs(readme): compress logo size --- logo.png | Bin 21516 -> 7987 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/logo.png b/logo.png index 5f4888c4ba851dbfbae9dd1eb77da5e153b39048..20b4fd6b5615c2d39231e7ad991abd5ce67ebcc8 100644 GIT binary patch literal 7987 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 literal 21516 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 DV^9bK From 5ce686113b29d5b275bcf3a46d5896a26ec23595 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 12:46:36 +0200 Subject: [PATCH 012/912] docs(readme): add example on using vendor tool for version lock --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index e35c2f9..494cc8b 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,38 @@ import "github.com/gin-gonic/gin" import "net/http" ``` +### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) + +1. `go get` govendor + +```sh +$ go get github.com/kardianos/govendor +``` +2. Create your project folder and `cd` inside + +```sh +$ mkdir -p ~/go/src/github.com/myusername/project && cd "$_" +``` + +3. Vendor init your project and add gin + +```sh +$ govendor init +$ govendor add github.com/gin-gonic/gin@v1.2 +``` + +4. Copy a starting template inside your project + +```sh +$ cp ~/go/src/github.com/gin-gonic/gin/examples/basic/* . +``` + +5. Run your project + +```sh +$ go run main.go +``` + ## API Examples ### Using GET, POST, PUT, PATCH, DELETE and OPTIONS From 84fc31f9f8e18e44bf0be5aae6ed64b50a60a373 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 14:35:10 +0200 Subject: [PATCH 013/912] docs(readme): move contribution guide to a separate file --- CONTRIBUTING.md | 12 ++++++++++++ README.md | 13 ------------- 2 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..eaa55a3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,12 @@ +## Contributing + +- With issues: + - Use the search tool before opening a new issue. + - Please provide source code and commit sha if you found a bug. + - Review existing issues and provide feedback or react to them. +- With pull requests: + - Open your pull request against master + - Your pull request should have no more than two commits, if not you should squash them. + - It should pass all tests in the available continuous integrations systems such as TravisCI. + - You should add/modify tests to cover your proposed code changes. + - If your pull request contains a new feature, please document it on the README. \ No newline at end of file diff --git a/README.md b/README.md index 494cc8b..2200d9d 100644 --- a/README.md +++ b/README.md @@ -988,19 +988,6 @@ func main() { } ``` -## Contributing - -- With issues: - - Use the search tool before opening a new issue. - - Please provide source code and commit sha if you found a bug. - - Review existing issues and provide feedback or react to them. -- With pull requests: - - Open your pull request against master - - Your pull request should have no more than two commits, if not you should squash them. - - It should pass all tests in the available continuous integrations systems such as TravisCI. - - You should add/modify tests to cover your proposed code changes. - - If your pull request contains a new feature, please document it on the README. - ## Users Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. From 912a7df572192e140e27854a57b1ad2cf80d3037 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 16:28:37 +0200 Subject: [PATCH 014/912] docs(readme): fix step in vendor tool example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2200d9d..8275383 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ $ govendor add github.com/gin-gonic/gin@v1.2 4. Copy a starting template inside your project ```sh -$ cp ~/go/src/github.com/gin-gonic/gin/examples/basic/* . +$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go ``` 5. Run your project From de1fdfd1e54eb09f33b3a62a1b3ad6a955847351 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 16:34:52 +0200 Subject: [PATCH 015/912] docs(readme): fix embedmd build --- CONTRIBUTING.md | 3 ++- README.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eaa55a3..4583205 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,9 +4,10 @@ - Use the search tool before opening a new issue. - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. + - With pull requests: - Open your pull request against master - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. - - If your pull request contains a new feature, please document it on the README. \ No newline at end of file + - If your pull request contains a new feature, please document it on the README. diff --git a/README.md b/README.md index 8275383..8b7d9b2 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ $ mkdir -p ~/go/src/github.com/myusername/project && cd "$_" ```sh $ govendor init -$ govendor add github.com/gin-gonic/gin@v1.2 +$ govendor fetch github.com/gin-gonic/gin@v1.2 ``` 4. Copy a starting template inside your project From 1923b3598377f53e0dd2e108e7f52761e3a69692 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 2 Jul 2017 09:41:06 -0500 Subject: [PATCH 016/912] update document format. (#964) Signed-off-by: Bo-Yi Wu --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4583205..547b777 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ - Review existing issues and provide feedback or react to them. - With pull requests: - - Open your pull request against master + - Open your pull request against `master` - Your pull request should have no more than two commits, if not you should squash them. - It should pass all tests in the available continuous integrations systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. From 89f0acc2f853be6e5af92a247e87a6d66613b819 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 16:54:49 +0200 Subject: [PATCH 017/912] docs(auth): add missing logs for auth.go --- auth.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth.go b/auth.go index 125e659..5f6d68e 100644 --- a/auth.go +++ b/auth.go @@ -10,9 +10,11 @@ import ( "strconv" ) +// AuthUserKey is the cookie name for user credential in basic auth const AuthUserKey = "user" type ( + // Accounts defines a key/value for user/pass list of authorized logins Accounts map[string]string authPair struct { Value string From 114b71868ab46a299c1d9daa8aa20e2f5e7018b6 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 16:59:21 +0200 Subject: [PATCH 018/912] refactor(deprecated): remove getcookie --- deprecated.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/deprecated.go b/deprecated.go index 27e8f55..7b50dc7 100644 --- a/deprecated.go +++ b/deprecated.go @@ -5,14 +5,10 @@ package gin import ( - "github.com/gin-gonic/gin/binding" "log" -) -func (c *Context) GetCookie(name string) (string, error) { - log.Println("GetCookie() method is deprecated. Use Cookie() instead.") - return c.Cookie(name) -} + "github.com/gin-gonic/gin/binding" +) // BindWith binds the passed struct pointer using the specified binding engine. // See the binding package. From 484acfc30396fa215376f9c2454d1bdf14c86c29 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 17:05:21 +0200 Subject: [PATCH 019/912] docs(logger): add missing inline docs --- logger.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/logger.go b/logger.go index dc6f141..a03cde6 100644 --- a/logger.go +++ b/logger.go @@ -25,14 +25,17 @@ var ( disableColor = false ) +// DisableConsoleColor disables color output in the console func DisableConsoleColor() { disableColor = true } +// ErrorLogger returns a handlerfunc for any error type func ErrorLogger() HandlerFunc { return ErrorLoggerT(ErrorTypeAny) } +// ErrorLoggerT returns a handlerfunc for a given error type func ErrorLoggerT(typ ErrorType) HandlerFunc { return func(c *Context) { c.Next() From 92ddc7d2405b441d5fc9b64e938628dec1382759 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 2 Jul 2017 17:06:53 +0200 Subject: [PATCH 020/912] docs(test): add missing inline docs --- test_helpers.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test_helpers.go b/test_helpers.go index e7dd55f..2aed37f 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -8,6 +8,7 @@ import ( "net/http" ) +// CreateTestContext returns a fresh engine and context for testing purposes func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { r = New() c = r.allocateContext() From f0b54a30238973ddb0bc9c9a58d8b7af038139b1 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 3 Jul 2017 00:55:21 -0500 Subject: [PATCH 021/912] fix: update autotls example. (#965) --- README.md | 2 +- examples/auto-tls/example2.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b7d9b2..de38839 100644 --- a/README.md +++ b/README.md @@ -910,7 +910,7 @@ func main() { Cache: autocert.DirCache("/var/www/.cache"), } - log.Fatal(autotls.RunWithManager(r, m)) + log.Fatal(autotls.RunWithManager(r, &m)) } ``` diff --git a/examples/auto-tls/example2.go b/examples/auto-tls/example2.go index ab8b81e..0171868 100644 --- a/examples/auto-tls/example2.go +++ b/examples/auto-tls/example2.go @@ -22,5 +22,5 @@ func main() { Cache: autocert.DirCache("/var/www/.cache"), } - log.Fatal(autotls.RunWithManager(r, m)) + log.Fatal(autotls.RunWithManager(r, &m)) } From df67c644795eaa050b378df62129eb08bd98d0ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 4 Jul 2017 10:08:41 +0800 Subject: [PATCH 022/912] fix cleanPath spell (#969) --- path.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/path.go b/path.go index d7e7458..e3424b1 100644 --- a/path.go +++ b/path.go @@ -5,7 +5,7 @@ package gin -// CleanPath is the URL version of path.Clean, it returns a canonical URL path +// cleanPath is the URL version of path.Clean, it returns a canonical URL path // for p, eliminating . and .. elements. // // The following rules are applied iteratively until no further processing can From baf216e1675df21daf66e4b9e904e13116f09a35 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 4 Jul 2017 03:12:29 -0500 Subject: [PATCH 023/912] add error check. (#972) Signed-off-by: Bo-Yi Wu --- examples/upload-file/multiple/main.go | 18 +++++++++++++++--- examples/upload-file/single/main.go | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go index 2f4e9b5..2258834 100644 --- a/examples/upload-file/multiple/main.go +++ b/examples/upload-file/multiple/main.go @@ -17,16 +17,28 @@ func main() { email := c.PostForm("email") // Multipart form - form, _ := c.MultipartForm() + form, err := c.MultipartForm() + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) + return + } files := form.File["files"] for _, file := range files { // Source - src, _ := file.Open() + src, err := file.Open() + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("file open err: %s", err.Error())) + return + } defer src.Close() // Destination - dst, _ := os.Create(file.Filename) + dst, err := os.Create(file.Filename) + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("Create file err: %s", err.Error())) + return + } defer dst.Close() // Copy diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go index 9acf500..1e9596c 100644 --- a/examples/upload-file/single/main.go +++ b/examples/upload-file/single/main.go @@ -17,12 +17,24 @@ func main() { email := c.PostForm("email") // Source - file, _ := c.FormFile("file") - src, _ := file.Open() + file, err := c.FormFile("file") + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) + return + } + src, err := file.Open() + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("file open err: %s", err.Error())) + return + } defer src.Close() // Destination - dst, _ := os.Create(file.Filename) + dst, err := os.Create(file.Filename) + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("Create file err: %s", err.Error())) + return + } defer dst.Close() // Copy From ecf1b37850461f680d48ba1cd1351e79b26f742a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 4 Jul 2017 16:17:54 +0800 Subject: [PATCH 024/912] add doc.go (#970) --- doc.go | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 doc.go diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..01ac4a9 --- /dev/null +++ b/doc.go @@ -0,0 +1,6 @@ +/* +Package gin implements a HTTP web framework called gin. + +See https://gin-gonic.github.io/gin/ for more information about gin. +*/ +package gin // import "github.com/gin-gonic/gin" From 735748b359cee5e7f7597128580f6f94bbc0987f Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 4 Jul 2017 16:36:12 +0800 Subject: [PATCH 025/912] fix(tests): TestDebugPrintLoadTemplate use assert.Regep instead of assert.Equal (#973) --- debug_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug_test.go b/debug_test.go index c30081c..2b01085 100644 --- a/debug_test.go +++ b/debug_test.go @@ -74,7 +74,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl")) debugPrintLoadTemplate(templ) - assert.Equal(t, w.String(), "[GIN-debug] Loaded HTML Templates (2): \n\t- \n\t- hello.tmpl\n\n") + assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String()) } func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { From 7d043cedb1ba33756ae9b69fa554a3062bff70d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 5 Jul 2017 09:55:50 +0800 Subject: [PATCH 026/912] improved swap (#974) --- tree.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tree.go b/tree.go index a39f43b..55eab99 100644 --- a/tree.go +++ b/tree.go @@ -105,9 +105,7 @@ func (n *node) incrementChildPrio(pos int) int { newPos := pos for newPos > 0 && n.children[newPos-1].priority < prio { // swap node positions - tmpN := n.children[newPos-1] - n.children[newPos-1] = n.children[newPos] - n.children[newPos] = tmpN + n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1] newPos-- } From 22fc0284e3ae2101f44e8188dde8cad797ce51e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 5 Jul 2017 10:42:56 +0800 Subject: [PATCH 027/912] update readme (#976) --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de38839..80158c8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) ```sh -$ cat test.go +# assume the following codes in example.go file +$ cat example.go ``` ```go @@ -32,6 +33,11 @@ func main() { } ``` +``` +# run example.go and visit 0.0.0.0:8080/ping on browser +$ go run example.go +``` + ## Benchmarks Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) From d535fcd59842094ea88307eafc48bc163c6a2f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 5 Jul 2017 15:47:36 +0800 Subject: [PATCH 028/912] separate type define (#975) --- auth.go | 18 +++---- errors.go | 14 +++--- fs.go | 15 +++--- gin.go | 119 ++++++++++++++++++++++----------------------- response_writer.go | 46 +++++++++--------- routergroup.go | 60 +++++++++++------------ 6 files changed, 132 insertions(+), 140 deletions(-) diff --git a/auth.go b/auth.go index 5f6d68e..410fe5c 100644 --- a/auth.go +++ b/auth.go @@ -13,15 +13,15 @@ import ( // AuthUserKey is the cookie name for user credential in basic auth const AuthUserKey = "user" -type ( - // Accounts defines a key/value for user/pass list of authorized logins - Accounts map[string]string - authPair struct { - Value string - User string - } - authPairs []authPair -) +// Accounts defines a key/value for user/pass list of authorized logins +type Accounts map[string]string + +type authPair struct { + Value string + User string +} + +type authPairs []authPair func (a authPairs) searchCredential(authValue string) (string, bool) { if len(authValue) == 0 { diff --git a/errors.go b/errors.go index 896af6f..599d458 100644 --- a/errors.go +++ b/errors.go @@ -23,15 +23,13 @@ const ( ErrorTypeNu = 2 ) -type ( - Error struct { - Err error - Type ErrorType - Meta interface{} - } +type Error struct { + Err error + Type ErrorType + Meta interface{} +} - errorMsgs []*Error -) +type errorMsgs []*Error var _ error = &Error{} diff --git a/fs.go b/fs.go index 1264582..c1693dd 100644 --- a/fs.go +++ b/fs.go @@ -9,14 +9,13 @@ import ( "os" ) -type ( - onlyfilesFS struct { - fs http.FileSystem - } - neuteredReaddirFile struct { - http.File - } -) +type onlyfilesFS struct { + fs http.FileSystem +} + +type neuteredReaddirFile struct { + http.File +} // Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally // in router.Static(). diff --git a/gin.go b/gin.go index c4118a4..e7b1fc1 100644 --- a/gin.go +++ b/gin.go @@ -33,67 +33,66 @@ func (c HandlersChain) Last() HandlerFunc { return nil } -type ( - RoutesInfo []RouteInfo - RouteInfo struct { - Method string - Path string - Handler string - } +type RouteInfo struct { + Method string + Path string + Handler string +} - // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. - // Create an instance of Engine, by using New() or Default() - Engine struct { - RouterGroup - delims render.Delims - 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. - // For example if /foo/ is requested but a route only exists for /foo, the - // client is redirected to /foo with http status code 301 for GET requests - // and 307 for all other request methods. - RedirectTrailingSlash bool - - // If enabled, the router tries to fix the current request path, if no - // handle is registered for it. - // First superfluous path elements like ../ or // are removed. - // Afterwards the router does a case-insensitive lookup of the cleaned path. - // If a handle can be found for this route, the router makes a redirection - // to the corrected path with status code 301 for GET requests and 307 for - // all other request methods. - // For example /FOO and /..//Foo could be redirected to /foo. - // RedirectTrailingSlash is independent of this option. - RedirectFixedPath bool - - // If enabled, the router checks if another method is allowed for the - // current route, if the current request can not be routed. - // If this is the case, the request is answered with 'Method Not Allowed' - // and HTTP status code 405. - // If no other Method is allowed, the request is delegated to the NotFound - // handler. - HandleMethodNotAllowed bool - ForwardedByClientIP bool - - // #726 #755 If enabled, it will thrust some headers starting with - // 'X-AppEngine...' for better integration with that PaaS. - AppEngine bool - - // If enabled, the url.RawPath will be used to find parameters. - UseRawPath bool - // If true, the path value will be unescaped. - // If UseRawPath is false (by default), the UnescapePathValues effectively is true, - // as url.Path gonna be used, which is already unescaped. - UnescapePathValues bool - } -) +type RoutesInfo []RouteInfo + +// Engine is the framework's instance, it contains the muxer, middleware and configuration settings. +// Create an instance of Engine, by using New() or Default() +type Engine struct { + RouterGroup + delims render.Delims + 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. + // For example if /foo/ is requested but a route only exists for /foo, the + // client is redirected to /foo with http status code 301 for GET requests + // and 307 for all other request methods. + RedirectTrailingSlash bool + + // If enabled, the router tries to fix the current request path, if no + // handle is registered for it. + // First superfluous path elements like ../ or // are removed. + // Afterwards the router does a case-insensitive lookup of the cleaned path. + // If a handle can be found for this route, the router makes a redirection + // to the corrected path with status code 301 for GET requests and 307 for + // all other request methods. + // For example /FOO and /..//Foo could be redirected to /foo. + // RedirectTrailingSlash is independent of this option. + RedirectFixedPath bool + + // If enabled, the router checks if another method is allowed for the + // current route, if the current request can not be routed. + // If this is the case, the request is answered with 'Method Not Allowed' + // and HTTP status code 405. + // If no other Method is allowed, the request is delegated to the NotFound + // handler. + HandleMethodNotAllowed bool + ForwardedByClientIP bool + + // #726 #755 If enabled, it will thrust some headers starting with + // 'X-AppEngine...' for better integration with that PaaS. + AppEngine bool + + // If enabled, the url.RawPath will be used to find parameters. + UseRawPath bool + // If true, the path value will be unescaped. + // If UseRawPath is false (by default), the UnescapePathValues effectively is true, + // as url.Path gonna be used, which is already unescaped. + UnescapePathValues bool +} var _ IRouter = &Engine{} diff --git a/response_writer.go b/response_writer.go index fcbe230..c9d07c3 100644 --- a/response_writer.go +++ b/response_writer.go @@ -16,36 +16,34 @@ const ( defaultStatus = 200 ) -type ( - ResponseWriter interface { - http.ResponseWriter - http.Hijacker - http.Flusher - http.CloseNotifier +type ResponseWriter interface { + http.ResponseWriter + http.Hijacker + http.Flusher + http.CloseNotifier - // Returns the HTTP response status code of the current request. - Status() int + // Returns the HTTP response status code of the current request. + Status() int - // Returns the number of bytes already written into the response http body. - // See Written() - Size() int + // Returns the number of bytes already written into the response http body. + // See Written() + Size() int - // Writes the string into the response body. - WriteString(string) (int, error) + // Writes the string into the response body. + WriteString(string) (int, error) - // Returns true if the response body was already written. - Written() bool + // Returns true if the response body was already written. + Written() bool - // Forces to write the http header (status code + headers). - WriteHeaderNow() - } + // Forces to write the http header (status code + headers). + WriteHeaderNow() +} - responseWriter struct { - http.ResponseWriter - size int - status int - } -) +type responseWriter struct { + http.ResponseWriter + size int + status int +} var _ ResponseWriter = &responseWriter{} diff --git a/routergroup.go b/routergroup.go index f22729b..89ec89a 100644 --- a/routergroup.go +++ b/routergroup.go @@ -11,39 +11,37 @@ import ( "strings" ) -type ( - IRouter interface { - IRoutes - Group(string, ...HandlerFunc) *RouterGroup - } +type IRouter interface { + IRoutes + Group(string, ...HandlerFunc) *RouterGroup +} - IRoutes interface { - Use(...HandlerFunc) IRoutes - - Handle(string, string, ...HandlerFunc) IRoutes - Any(string, ...HandlerFunc) IRoutes - GET(string, ...HandlerFunc) IRoutes - POST(string, ...HandlerFunc) IRoutes - DELETE(string, ...HandlerFunc) IRoutes - PATCH(string, ...HandlerFunc) IRoutes - PUT(string, ...HandlerFunc) IRoutes - OPTIONS(string, ...HandlerFunc) IRoutes - HEAD(string, ...HandlerFunc) IRoutes - - StaticFile(string, string) IRoutes - Static(string, string) IRoutes - StaticFS(string, http.FileSystem) IRoutes - } +type IRoutes interface { + Use(...HandlerFunc) IRoutes - // RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix - // and an array of handlers (middleware) - RouterGroup struct { - Handlers HandlersChain - basePath string - engine *Engine - root bool - } -) + Handle(string, string, ...HandlerFunc) IRoutes + Any(string, ...HandlerFunc) IRoutes + GET(string, ...HandlerFunc) IRoutes + POST(string, ...HandlerFunc) IRoutes + DELETE(string, ...HandlerFunc) IRoutes + PATCH(string, ...HandlerFunc) IRoutes + PUT(string, ...HandlerFunc) IRoutes + OPTIONS(string, ...HandlerFunc) IRoutes + HEAD(string, ...HandlerFunc) IRoutes + + StaticFile(string, string) IRoutes + Static(string, string) IRoutes + StaticFS(string, http.FileSystem) IRoutes +} + +// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix +// and an array of handlers (middleware) +type RouterGroup struct { + Handlers HandlersChain + basePath string + engine *Engine + root bool +} var _ IRouter = &RouterGroup{} From 31ac11a29891bf2d7c2da20054507f3fb5350a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 5 Jul 2017 17:37:24 +0800 Subject: [PATCH 029/912] update comment method (#977) --- auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth.go b/auth.go index 410fe5c..7ebf557 100644 --- a/auth.go +++ b/auth.go @@ -89,6 +89,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 } From 2a36eefcffe31ad16d9b27fdc64c083e246e80f0 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Jul 2017 20:37:28 +0800 Subject: [PATCH 030/912] add test case of mode --- mode_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mode_test.go b/mode_test.go index 2a23d85..bf6a5a1 100644 --- a/mode_test.go +++ b/mode_test.go @@ -5,6 +5,7 @@ package gin import ( + "os" "testing" "github.com/stretchr/testify/assert" @@ -28,4 +29,9 @@ func TestSetMode(t *testing.T) { assert.Equal(t, Mode(), TestMode) assert.Panics(t, func() { SetMode("unknown") }) + + os.Setenv(ENV_GIN_MODE, DebugMode) + assert.Equal(t, ginMode, debugCode) + assert.Equal(t, Mode(), DebugMode) + os.Unsetenv(ENV_GIN_MODE) } From f843c333702ca0c52b6170b34b5b514f7e530521 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Jul 2017 21:05:02 +0800 Subject: [PATCH 031/912] add test case of mode --- mode_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mode_test.go b/mode_test.go index bf6a5a1..0a9cbc3 100644 --- a/mode_test.go +++ b/mode_test.go @@ -12,10 +12,14 @@ import ( ) func init() { - SetMode(TestMode) + os.Setenv(ENV_GIN_MODE, TestMode) } func TestSetMode(t *testing.T) { + assert.Equal(t, ginMode, testCode) + assert.Equal(t, Mode(), TestMode) + os.Unsetenv(ENV_GIN_MODE) + SetMode(DebugMode) assert.Equal(t, ginMode, debugCode) assert.Equal(t, Mode(), DebugMode) @@ -29,9 +33,4 @@ func TestSetMode(t *testing.T) { assert.Equal(t, Mode(), TestMode) assert.Panics(t, func() { SetMode("unknown") }) - - os.Setenv(ENV_GIN_MODE, DebugMode) - assert.Equal(t, ginMode, debugCode) - assert.Equal(t, Mode(), DebugMode) - os.Unsetenv(ENV_GIN_MODE) } From ee7b912a242e84896be96b3d921354a9b6cc6950 Mon Sep 17 00:00:00 2001 From: Regner Blok-Andersen Date: Wed, 5 Jul 2017 07:17:03 -0700 Subject: [PATCH 032/912] Updating IsDebugging docs to remove redudent words. (#868) --- debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug.go b/debug.go index a121591..b31ca68 100644 --- a/debug.go +++ b/debug.go @@ -15,7 +15,7 @@ func init() { } // IsDebugging returns true if the framework is running in debug mode. -// Use SetMode(gin.Release) to switch to disable the debug mode. +// Use SetMode(gin.Release) to disable debug mode. func IsDebugging() bool { return ginMode == debugCode } From e4fd80c627c9a63247373a5818f0628cb5ccc103 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Wed, 5 Jul 2017 16:22:58 +0200 Subject: [PATCH 033/912] docs(readme): add sections to template docs --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 80158c8..ecba666 100644 --- a/README.md +++ b/README.md @@ -645,6 +645,8 @@ templates/users/index.tmpl {{ end }} ``` +#### Custom Template renderer + You can also use your own html template render ```go @@ -658,6 +660,8 @@ func main() { } ``` +#### Custom Delimiters + You may use custom delims ```go @@ -666,7 +670,7 @@ You may use custom delims r.LoadHTMLGlob("/path/to/templates")) ``` -#### Add custom template funcs +#### Custom Template Funcs main.go From 71d3ae4f922f4d674bc4760b9b9eb290b645fb55 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Wed, 5 Jul 2017 22:50:33 +0800 Subject: [PATCH 034/912] fix(comment): remove todo --- gin.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gin.go b/gin.go index e7b1fc1..92a4144 100644 --- a/gin.go +++ b/gin.go @@ -345,7 +345,6 @@ func (engine *Engine) handleHTTPRequest(context *Context) { } } - // TODO: unit test if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { if tree.method != httpMethod { From 42a34cdc35f72b6b146f48242c52a875bf698dcb Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Wed, 5 Jul 2017 16:55:59 +0200 Subject: [PATCH 035/912] docs(context): document keys, errors, and accepted, close #488 --- context.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 5196bb1..d8c9af0 100644 --- a/context.go +++ b/context.go @@ -48,9 +48,15 @@ type Context struct { handlers HandlersChain index int8 - engine *Engine - Keys map[string]interface{} - Errors errorMsgs + engine *Engine + + // Keys is a key/value pair exclusively for the context of each request + Keys map[string]interface{} + + // Errors is a list of errors attached to all the handlers/middlewares who used this context + Errors errorMsgs + + // Accepted defines a list of manually accepted formats for content negotiation Accepted []string } From 22ee916dfecdaabe7b541b7f76bdc8c64e99c145 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Wed, 5 Jul 2017 18:25:37 +0200 Subject: [PATCH 036/912] docs(conduct): add code of conduct --- CODE_OF_CONDUCT.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4ea14f3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at teamgingonic@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ From b985857899c60f029f73331b81ce281a25724f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 6 Jul 2017 09:28:16 +0800 Subject: [PATCH 037/912] update func comment (#981) --- context.go | 2 +- errors.go | 11 +++++------ fs.go | 4 ++-- gin.go | 4 ++-- response_writer.go | 6 +++--- tree.go | 4 ++-- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/context.go b/context.go index d8c9af0..2134b8f 100644 --- a/context.go +++ b/context.go @@ -151,7 +151,7 @@ func (c *Context) AbortWithError(code int, err error) *Error { /********* ERROR MANAGEMENT *********/ /************************************/ -// Attaches an error to the current context. The error is pushed to a list of errors. +// Error attaches an error to the current context. The error is pushed to a list of errors. // It's a good idea to call Error for each error that occurred during the resolution of a request. // A middleware can be used to collect all the errors // and push them to a database together, print a log, or append it in the HTTP response. diff --git a/errors.go b/errors.go index 599d458..f38d3ff 100644 --- a/errors.go +++ b/errors.go @@ -69,7 +69,7 @@ func (msg *Error) MarshalJSON() ([]byte, error) { return json.Marshal(msg.JSON()) } -// Implements the error interface +// Error implements the error interface func (msg Error) Error() string { return msg.Err.Error() } @@ -78,7 +78,7 @@ func (msg *Error) IsType(flags ErrorType) bool { return (msg.Type & flags) > 0 } -// Returns a readonly copy filtered the byte. +// ByType returns a readonly copy filtered the byte. // ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic func (a errorMsgs) ByType(typ ErrorType) errorMsgs { if len(a) == 0 { @@ -96,17 +96,16 @@ func (a errorMsgs) ByType(typ ErrorType) errorMsgs { return result } -// Returns the last error in the slice. It returns nil if the array is empty. +// Last returns the last error in the slice. It returns nil if the array is empty. // Shortcut for errors[len(errors)-1] func (a errorMsgs) Last() *Error { - length := len(a) - if length > 0 { + if length := len(a); length > 0 { return a[length-1] } return nil } -// Returns an array will all the error messages. +// Errors returns an array will all the error messages. // Example: // c.Error(errors.New("first")) // c.Error(errors.New("second")) diff --git a/fs.go b/fs.go index c1693dd..8570a9a 100644 --- a/fs.go +++ b/fs.go @@ -29,7 +29,7 @@ func Dir(root string, listDirectory bool) http.FileSystem { return &onlyfilesFS{fs} } -// 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 } -// 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 92a4144..ca8d75e 100644 --- a/gin.go +++ b/gin.go @@ -285,7 +285,7 @@ func (engine *Engine) RunUnix(file string) (err error) { return } -// Conforms to the http.Handler interface. +// ServeHTTP conforms to the http.Handler interface. func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := engine.pool.Get().(*Context) c.writermem.reset(w) @@ -297,7 +297,7 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { engine.pool.Put(c) } -// Re-enter a context that has been rewritten. +// HandleContext re-enter a context that has been rewritten. // This can be done by setting c.Request.Path to your new target. // Disclaimer: You can loop yourself to death with this, use wisely. func (engine *Engine) HandleContext(c *Context) { diff --git a/response_writer.go b/response_writer.go index c9d07c3..216165b 100644 --- a/response_writer.go +++ b/response_writer.go @@ -95,7 +95,7 @@ func (w *responseWriter) Written() bool { return w.size != noWritten } -// Implements the http.Hijacker interface +// Hijack implements the http.Hijacker interface func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if w.size < 0 { w.size = 0 @@ -103,12 +103,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { return w.ResponseWriter.(http.Hijacker).Hijack() } -// Implements the http.CloseNotify interface +// CloseNotify implements the http.CloseNotify interface func (w *responseWriter) CloseNotify() <-chan bool { return w.ResponseWriter.(http.CloseNotifier).CloseNotify() } -// Implements the http.Flush interface +// Flush implements the http.Flush interface func (w *responseWriter) Flush() { w.ResponseWriter.(http.Flusher).Flush() } diff --git a/tree.go b/tree.go index 55eab99..750ffae 100644 --- a/tree.go +++ b/tree.go @@ -357,7 +357,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle n.handlers = handlers } -// Returns the handle registered with the given path (key). The values of +// getValue returns the handle registered with the given path (key). The values of // wildcards are saved to a map. // If no handle can be found, a TSR (trailing slash redirect) recommendation is // made if a handle exists with an extra (without the) trailing slash for the @@ -499,7 +499,7 @@ walk: // Outer loop for walking the tree } } -// Makes a case-insensitive lookup of the given path and tries to find a handler. +// findCaseInsensitivePath makes a case-insensitive lookup of the given path and tries to find a handler. // It can optionally also fix trailing slashes. // It returns the case-corrected path and a bool indicating whether the lookup // was successful. From 436d8e2af9198f5548a73cb96dbeb1cede403a5b Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Thu, 6 Jul 2017 09:30:09 +0800 Subject: [PATCH 038/912] Improve serveError code coverage (#980) --- routes_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/routes_test.go b/routes_test.go index 7464d5d..41693ee 100644 --- a/routes_test.go +++ b/routes_test.go @@ -439,3 +439,15 @@ func TestRouteRawPathNoUnescape(t *testing.T) { w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333") assert.Equal(t, w.Code, 200) } + +func TestRouteServeErrorWithWriteHeader(t *testing.T) { + route := New() + route.Use(func(c *Context) { + c.Status(421) + c.Next() + }) + + w := performRequest(route, "GET", "/NotFound") + assert.Equal(t, 421, w.Code) + assert.Equal(t, 0, w.Body.Len()) +} From ff5788bfdd513e659ee15db712a65370db4eb6ab Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Thu, 6 Jul 2017 08:41:27 +0200 Subject: [PATCH 039/912] docs(readme): upload full size logo --- README.md | 2 +- logo.png | Bin 7987 -> 87064 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ecba666..191546f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Gin Web Framework - + [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) diff --git a/logo.png b/logo.png index 20b4fd6b5615c2d39231e7ad991abd5ce67ebcc8..7942ad563f911cf8e828e140c549e1f7f7c34b8c 100644 GIT binary patch literal 87064 zcmV*NKw`g%P)~3^FMU?=H=$*;Nalr=jY$w-{JH5<@WvM_WSDQ<^Yh<0002?*&Y4< z{x_GXC8^*&qoUZaF8}}kK&;)tm}vLeAN1QD0FThut}NQFE%)0V_1qo+kkU4mr#F|T zKCRm}mZd?g+dZVCK&#yU|NX+4Y9y=P2nYuvA|e6;0>7APLqkJ8qoj$6i4hSIR#sMz zkBRYNl9#MY$zxw$H&KNYHDD3!%hQc@O!vtM6d3wfv+85y*+v>}bX5PhsLoXie-s+N|Pv9Yn2mzNwI z943;(#l^)Qi@OniuKN1=;^N{XkiX{U<`aOgG?=ID?d>Fz!SnO;@bK{I>FG0;rS;+>|?;3}ci=;N{#nbYRu!xes__xJbW;@6PJ-T-Wx*52Vlsn_`0A?D-GIi$-7 zbfE9xeDmE!%G=`X;g5-K9JWq;Pa`hs==HnVN)P!X=eQNvem?}oTXVrHw_#Y1kmgD zmUtg@pm-#bzO2ZEa-VqsLtwJSO?qS)Ur!TRPPbG5000kkQchC6lX7(Zg_&;owoQ%t7Nn|Ghz`xIPxYVZLTp>5|^Ue?e z{Og*g;nkw)c30B;yA16?0PqhBfw|^pe3YDdcct`1yY$lt0RB-N(&4#g=84*Li!14H zTdG>UmH^+w7S*XcqxMmph$K znfZj1ppo7Rf&Exrv?l3v1+o0Q#`}5#fNr*X4khkn!(4k``@nAnZQ*%iyMoOXBtK7A zEwdL80CcTwWX^kw^bkJix~GRToD*%)OFF`4RTAAx0)Q?xnBIDui3_14>u$fp2o2}x z3f7wIMRRp?2>`m%$aewHU$9(LZlV)n)2$WTxA%NWHS;zT0Cb-*)SAklRWl!ql8;mp z_g33$cd1H?$N>U?ZZnwP8ONU&yk8Za1-g45=n^d-XG>o!>M^7a&}}-SvgXgLQ{^6! zXhH2-ViRes=bq{$SB}|10MJD```Lqh;LppIn+I9z-62{to*r6TZ*T3GJOj{0_EU!q z{COdlT2pfF!*}#_*l>C}t>m!BHV**ZV-R)N!|eGpLSWx;!#ufoM;B|<+fjB&CFW<# z2mrdnaq9HAZayFA9O@kEo*ylbiEG7FUacK|;lwN(Ys*C>-6r%Q0O$g1snfjR`RhFM zNoS!f;%v3c`YIW)biT6~w@n`6EKG<6=mzts(@bCUS6Q1sd4fdy5^N@OD>Y#JMQ3m7 zdW&+20N`(%Mjx2Yfxphz%njG;21Olavu6EfWd>}C?FgJE=CH{!z~9z`KGf9Q@YmIv zv1`J5+c8~d#ht7MR_69W$uu4BuyPgPuNzArnC-bqvHlgFee?Fm^e8b&S2s(WbhB=j zZM2-5dnDfh{<1mrp(8=V_v;$ZjgG2c)n;3J>U*pA?i`G7KLnkt2NMANJ%{N-<9zV^ zRdooy(MCNm@7I-UI;^&->Tttyaki>$47Obb_-l?)fxk^iN5j6+PtPoj@^*^Mhyeqt zDwQhVt2wh{W}&`%I03-lv5!8qk%Knr-zaz;jN4+<;*x18%$43AjJ0*^`b~;wSfUQ_ zcbuofu)wwV{O#zR^&PXz~8nga6Z0W@Yb$3JU=E*QrS|q2IzTz ziLO|UF<0&;0QjS(QAwk=3H+@*huUs@MOUP2Ircu%2j<@UoiQ#|TC;TALi4+N5nbnzem|y6^5sWsn!;+V&)9MAJUgf z-0%;w1m43O%WsqGae7r`rX(37F@?LO-1KC3qY!0*xO?)c;?>iyOw?&uS^PbGgq=`t^Py;fZyseedKWn{L{AS^7Hny z?s3w9>I1iltn*@fFSg%=+*5sMjaaP?sk@Z~0Dsh>z`sD4 zVB_d-FB0FdcmF<87GWH~#~3xS`%W>NJWu9FOF?4vt`I?U=b6)>21G#ALqz3JA!-x_ zK?y{#p;4qEMq^BDw6n6XGbT0`CiX&NWBen0Z)R_A_ubpQ-S^$I`hM75$nD;C56{dz zGxIEV2K*dylso71=Pno`+BS=3NQ5RuON!;Z_pOxBviAnJ!mj!?>rI>-`?3*jpR=ZJ z4x*Ta9OK@H-rrA_Kp$9m_m%P)UJXn1KY9&-qAe91==My`bM>6q=R19VbouS-=RVKW zZ{KZ*Xj?3sAy6n<(})G{LU8MHJ^fhe>QFqCySH?C?;kcqv@MoQ-6a8%3R)5Y)>8FA zpa*~Ur;QDAr`$lGJAc3s(Kfil47sVA5Yd__=6qiuyHZcTeYmsUr|D_e_kP|Gd({xp z?A>qbURD5-&{hPnm`mkZj_ERI-}_^ycx3HI{ed3bq9LN0`@+;+MbZ=nt%(Z4#vHv@ zPj~*=E1m4+a?wmt&bRP%I??PMGDCg?6p)CPQUY+gKhRz9CE9)1{YJIt{qwx)zA?1V z@9i^0G-Jn2-AYOk0!3@8lFsxmwuSBqmRwI;de43od}ZVP{0#RQBATt?{J97qAqiU3 zs8Pbe9K4dp=XtLFSE-`Afs|hTxon7NmJXXC5i=qZ0eQ6Sf6W0snc$x^L^MO6nz|F1 z6$Mn~stciHb2lk2$PsO~LxNcmRL+(2=+%pxkoSJ4A);+;#PpCTW!%Rg$vENTQN3S^VxNnANn+~o$l_052Sc^cBbl__) z<&!JZ`@j&tFLvYhtZ_XHAGep$PoT*tt1SGZ?ybA;sTiIbNUuujHN!9b|+)i~$K9pM| zZ{1~xsEK<_-46*02ohQl#KX4Iv1>j~?>AQ01Ln403#j+`dyzK6(=Yk=kS(pbDqKY;I8V}eFXQZ55Mcn5@qd9uHSMpy& zMALi6)Q!pIDih~=0c@Xx5L86*$XY@VYkX)hgkLp8)c%0#F^sopk1MvO@|NxQwMJBu zEQee!%P$QU<@|L+L{oa#)E$LjG=0I^6-*$ey|!nE?d*k?vB2x0bYE(e(OvNDyht>) zv!>=epb;aezOuva*rgNQC6F1BmwbO2;P_bU>nubj~ODG$`Mm{4vLXdJYlUp zJyAP!wF7nuy5>S&TJz2Jy!+sJuM|iXB2Zb#g|(hXBa%V^JrkaN zz8ka6>gg8^5w%L_R{%xC2p95vdmdX60FT%KE2xxi>;Bt#(p8$(*{a7AhKO1u^cz?c zF@nur#RO8v_UYJD`Fj;eIC;O~K=7gY#+f$^5j9WfZ$c#|**r($;;X6Y_6S!gOG$m9tqD~`r+m6j3 z*owdox~%A>UB2?eAkGIngkq``~#eEYWy(K4a>Z5)?JDY-dv( z&mmj=v7d^>G%m~)z(=;7k(}~7AX@*<@(ujMT^%vW-kQ9nh6gl17 ztW!q&XC+|-k%&KTxzM1yst;U`BO331GvuIRm_l>(pRzsU`#LBh6wCda;&v)V69Dfv z>!a)|T#q9f?Kv~-O-K~f#PWKyJK3%*X%vhRQ8{U|@*B^L|DQn=kNXCJo_n{f7$O>B zvpl#Dsu-dPSiiTqlLPlF#4X}nQ?yzBmCmaPqp2oD;Hf4Z9dhGEUh_^g!W}Q0Vc$d3 zh;o@6vOPCqQa~_vlK%Jl%3bBn@%cOgNP4uz5gstrHc?(Q~MY(xHM8p1L@7`S`siHWJU&UAYt)K>ehg;{S z1_O&|pu4Mxii(cty3Y98b=TGPjrd%jyRxWjVB#|naS_2pP0a-nObi5bF*4Bq!Jg{s z9;d6ze$Y-$g-^Ef*Zm(q~x?YLPv_B-&}^v=_Gh=n2G)_zZeO3Cb@6Eh7MJXCqtHH zqt1kJP9+mKqtvri44pzCjQ^r%ipCWPa3(Xqt&ZDyI=4I#vMk%}hAs%NXYh^tctWENitwZ$mOcqD_xCz0-=~00xHWCa~v=+NVeu4v5L*x75AZ z=9s+alCW9!Z?}i>&O(xs5H076VU@?L9mfzDox}}wV(K9n+~thv8xmi^0JVZ37)qY@_Y z^kgxxk-(V1;E~?FSAqbEDLG(zd^>PwYP}d^U34y2dC#(e_d|!DjZtO-!~FH(PTcKG z!#P+KS2!3rQ!^K?5Bt5QmSMvpQ69kMLzxvV>vO>VTf0Nq^-YzjzBV!U?LT*!sQw1k-h{kF(4 z&Q@ur_qs1+Sr)>+(BV3S7{@s}+;TsLNlYVwBges+mSZNNt72KzUo&K@mfP3sA2WP;%Uufq?@&TGiX=<+S?4fA4SJ3|Y4F9uHk!HbY_z@LYA$#r$UzlU?4_0n0>$2l!H3YqDh7eFo7{(oY-D+&r=olnvi8H?vl{uJeZ6& z!54!S`eV+5nK%Dd7{(5X)93E71MJ$@YsTnfSF!(H1 zajvF|*M=-xWlx4qTYybyh`M$VW5LXk!LyH=A$Az11LYj)sxem8iy_Na*p1zLbe*Ya zNnF#Uv)tt@Ua|zb;!Rm%7vtHonlD}TaLBUN^l|Y8k;=Eck9udyMIl#W+YIYT+ zG?0He*VH{$@$HahD{DP;*@qGcF-p?=%?kZWjQ@j)pNisn17QrvjLmXpRawO{r>kZw z?26Fogwd^N{Fsb|lR(_IR@{%o4g>H9x8t^FhP?Eu_f=J|g)Cc57l*!$GwrpzB{~>4 zH7oSn5R2!+ldrE8#SefOGP9Hpl`&S^W@?yT#_F$-Wvl6q(06Nlw0i-ggVOPSpsynm z3ny^iD0*(jjUfS$-t2dCm@U$`UpIv;TSbRLveZEL%M1|GyP!5vek1wG#+Rrw(KV%I{eragggoej0KaKng?uUPTJk3+5Gxg7kQr{ zi>;sZR{a^WZ1o)Jt{L}^45P!lMeSj8kip{VWYLF>OKLk4YcnLZ^jGO+-gwVe&*!0W zOYb@t{_R=>Mu)@o3Vok2Qc5hIy){UI{Qx9}Tr!fq(XabFt=Lob#PW}jWvk}K(72!# z=#QE)jvVLNrl}GOXMj0yS1-)#<-yC0^=GW(c2=>xIb_*txi>Vftc%0#8qkGxMPky~ zelv8$f|*GIboTP1@s^7*Zt7QfpFJ;qJ!IKx**~1Zx2nv13x?5_0BsaK>nxrS0^Ym4 z7}!K4M#-uet9kO$^AqpciupP;HtP3J!jAg^5^c#0PSlI%m-(MKys%ab{LC2cI=3CP z^W>!;4_UTKt__Ws+8J#?0%wFF-2X0m4klzQoG^4vvBMb4Hs`D~lY1^^tL4+(>1!W^ z9ru8fI(wk47i-1){2vnT@N}_b9faEV;rr?}`pl~GOvtj;aZP9(Y^9~&ZgM3|jPt?v z18V??1v3W@_S0qhyY5G1VhnM8S;qPD(r*u0wkl4HRE9hjh7KbeAIN6rTwfH&JwY;K z!5ndKQEQIT&(c{~{?0iHP}Dsd?+RJADjpAwQ-c-z&{K&H=5oDO9RIEVGYjLGnH>4i zCd3=>8AeM2vs3l+Zap*Bj;qRVLzb?OpvnL&lL}F27fF(0=rudi33*k7U z%p9q#gM6lmW{wgI40HhwuQvru|9v_ z3|Y4Pt_}kih1z{#U<0F@3Ba6`0I?vJkfZC05hsy3ZH5eZZ98ns?bno=EcRG_6|!vk zog3}ObXyoWh$+!QLT;dh85oOVX6W=_2X5#$fn&5u=3>^FdmZQ1_B`OSkY&s6;#Ony zk3-`SphSmc<^~uh$(#jA270)T@_vn~zHf!)~*fRzsGZV*}aeT+xZDNw+ z^={leO&8w{S+?A6+Nl9MI&(iI62@K;7?|+W#i+~IF(yc-VttT4N6W=BWZ81t2#xc@ z?*j;oJ!6=_@H^UZUVP+<3G{2d?LPa|y{cla{G2VfH$vk`YZLw?-mWY@>4D(4!};{zefme=*I z0sX1axYckOCiaNT436x?_l`}>n8-9&HQ1`Byf$Rn^4cF7Tdfil!f=7uBbMasUmFwW zPf2FZ1P13~RnDwVR*fe@mMyET)`0#*Xq@ux%MS7(oy0YdoI>mz&ZwF5~A#3VxN)f|Cd)vu!=%a+rj)_^{ATI?AK z5NtkJw5{EM%uUWjCVRM==gGLL>b;!~S+<<63ysf0=|M3bsgBF4mZCQVgfUi?w@DsUd8RVD?*knqhqZB z{n5}kN@A}VfWKXg8_f^pt=HPBwbo0$8_r+7esL5LU8X& z1pOmS-nqogWHOm|lIq0g)48N2VY)dv&-I++ZC~9fjPoTTxISNeYjqETC>*CdNBmMC z(6hV^TUJgb-jj{!R9iy7ySd2*&iUfNsnT+G${kdDc4YuI)N`OWZo*V{ge2Lx4z?}M z9Ro1dnanUL0&kU;bH>4p)iMnDW?>|r728k$BqYg3Gu5_-eo?4el z=!u&}yD6(w79K^ev3(PgWFvVk)a(omTSCn+5f>8c%#rPz5At^R17HGr_-O9M0vQF_Se6%)C{hi!lCGsE8_1SWx0t|&Lhddz)>#yQ zj9WIfmwUbXr3ixPPEogHS&=}mb#!_%B*{iG6l!*~h5j2D>&;~V!<*WRMs2#y3`P1f znjeiTbQHEyo(xH{K@5kwxz^CXNvu1Q0XntJWwo8(I|y)y6y)s5Dn(Al<$c)Fwe)1e zX;0{%SrqHc0Mip!x0ic;7FUdbz>T7GI6ns4`^Y_CkUJTYWJ97~Y`yg78g>okCYM{}YfinGVEa#n16 zZ9d#bLXr%=(NH(ume9?C$a)b7T-wzL?@f)t`y{L37$)*(q!ageG>`LwvI|3!47@XK z?U=%BeeNy{H`YnSO!CEjwPvV)a5|dkMhFZCB$LsZYi>*=+~REzJz()-g1v+>^(CgqN`{T z1EMgaqX>SoQaGcht@LCAxIWaKYb`-h1q`BCKL#gCOIB6;k;uJ31Wc?KgLqSIW`1?Pi~ik#2*?0A-Izh#x4Trj%wYtwBI^M_a`hVxkIAHIlD=4K9fWHp|x)rXBBqk~8kPxX<}E0vc+k_@m9+WM%5 z!y$JMg((8y4$;8rKyV<>=d~ko)mac=v~V-P1O_5XhUG8*eEpn_0ADfn)5kt~Ryh@t zWN=M|hG#?FC81`U=!+3e;9>>e=W&`pO0Nnb45vji#7#Id3;^ljbB$9rw{-2d&Y7%m zM$anKAxQ?-{?P8FM!g9c1zAMVAv(s29xpYFKLQXXL>rmg{yD%g^2a>5zSJ}hE;@)H z^i@BBo?RT0WKc~n8L#J_T%4gBMTuO*Yi}L+T=RJQErO_d%YMHDk#9c&IdUQx4=ySH z4lX(|klyWMA3du)8j@r{?FvnohPp38Zf~R?CPjoCNk#{{r&KdEiU1IZNE;*K@Qdz- z2#Pkbqgk9Hcbl^U8P49~bM$gKU2I7P)xOZQHPpQqY90j_g$W7!_-+712XplrkP`@t zjm&GJg}w)z&C(U04v^FTaIv`8nV1)mBK<}!0))Cyw+0M}QwWZpt(#X!d>eljl0vRxrb z2GFjs=#AD!svykinZStR#ahPQbQFx`$iLVLG;WW4A{TBe&2MDMTF5xVB*tK>6AHU^z6-Mfw|_}6kYAg`#2Oj z88;v8mxm-7G%qjPj%h5^?3aPjD`8+ZdT*)j836m4<%liQNAMyaBy^zE>T#o+r}B-m z0!1S8W~}K3TQYD)!*Y&>njr(s>6H_OG}c%udl55}+j8F+;SunN(K7?~wNBKlbeCo2 zLX~Fn-w%(4BpEE1J`n!lp-}f>V_5GLD+*~?WLvZP+b@nmq@4svI2bDOIowhEn8j!p zT@5W{^WNjURtvTol4PJLYYxQ+cEG-pfAcfu3C!l4O9i#Q9LDd!UiwT|iW%SBhXym2%T}1CGmV`4&tdiiWzG z(8SWw`xc{{m-B!vFh|de!lzTP$spNZ4Y}4*aTCqWbRr_sI~RyN0EZ1gmOonv5#`Nr z@0AfT@W+?blzhwR!db}YMVc*E7FO%u2}v?Aj&$gw8g?|^BmMg7*u&(alQWPh0>W7< zKQzwA<$=DF?hY=js})#_TS(~H?vNyd;%sQKtF=hvjIjoUz|1gAm<-6@Bx8-lx0-Dq z7SNTZ3RQQ{79xG#yCF#i!W35DeSG)gKdwq~D94)NI6GAvYNs?lsXT7qW;81Hc4N-*;^57BI(*wc=Y#i}w6T7ZG;lfnG49R}O_F`A^$+ zFf8kMBix&I(Kj;z1`dGUc<6(Wn?nd=&5SHIRNsl{Zmu$~!0omCxO;gbB*}TSf8VxG zr|#c+@SfMM*%i9GVi=3o|$2nU`IP0uM>6l=wpE;a1LL>F>cSU)ea z?4EC>-y4!-eO>m*?13$(-gYpZhxT@B!cC7Q=u;`gcCo*9cF<26>LT&~Q z$dF55GI-&|@DCs2iu6wrLlJ21t&IUj7mjN@5A=fm3Q4lo4t@5~fwNN&ZQVR}?$&Fr z>ga}}p}4)xijYkQL`NPY2Dl)?B2nWAh!_SZTc-0z?-kgFh>MbX(jLk|c~CI{otOw(S#JhIc(N zbLaCzH;q5=+I81lwf?>-66no;zcVaxf=(O5ICYMMV`(27`kWghh%^~ zIXe2@>C?~6&c3mI`^ou)JbZZap_!SHk^Qt_zX6Jej=X;A%(>9bW&??$qGV^NAJj!?CjC)x6P;7g*-dFYjS?LJTtR(Bu}>UgXZz^z13>< z_W6*#_NxE2$xuLkAZ{kKn|{LtM4UxOhCiPU#f%BuDJ~i&Y7CFtN{e1$wLC__P4h9H z$8EOyYpQvvx%rngNnhO*cl5;kYSrwvh4Jy(;o(!0lQT2#zvDNv2z45A~fZd^p z&Gl36?W*_R_AZ9p%=Yhl;nmI6P~4vA(ddH_F+h%E#+~(SzG+iSAJr~mw8)Y9kj+cA z_u6K0;ivw8_gXG^9;a6IX6@;ZFYtJ5Oh(wA1O#JX6~O&qqN}s1iIq_nv$2Iin$+ zx5GRpe>XRWmTme;AR_5GCNmQVj?U=*ntO(3MC?ql9M{w#TYmi1`sb2Zzco5UUj2B@ zr+d$SzH;l6kG5r&m$e~{Eq8@Quv11fH_~!I0K(U7D|L@5e@!za;z*&rn9rN2z8_n$ zVnt6rdDwD3+`RT2IJ9B7@9D?eGRxP|u#BDf4bd98MMvLM-m9ewbTi!SN}+Z-^Bhny40{F2# z3EC!#as4ay{8K0-^OM6(;}%oQU@PQ@!j0|K z7v9))ZTZ&N9({PylO2<`es}}iR9kfYVIHHQA)mkEE;lf33IB^TYn&J{qeut?GbAYg zDL{Zp=8g~-FLfj{8mc7iU-v5lLNv_!CZje*pcjkvr^5~Fz^UQh<(I`De){0*v+IYB zZv5cno;&8uQzt*z+5N=o-P_Xrw?EjiW98(Hn~sj<_e*bHZ)iEtSH2~g028({{9jHV zk|Kg=2s}ar+yTcaapagw&%fK5t7R(qx0!(uO_(UgdGmo@jPx6_73}L5jvRciZ8>>x z>%lM9_aFb{YG}r18#k<8)p(MB=gO&1?zlb>CJs&h{2TbH&er))$=(ljJ^g~4MPwwx z|7l=s?T>CC00Wmu2#~-KNH4#6zPGd`Prk0@RWJw9D4A_bGd?*Pu70E4jeE9x^|obn zjb$>xhu_%w{e7VYFReYc`{DX+8rF~0+Pz#H+Vx!94X9rJtzTOk!YPsfvyEXPJ>A>) z#gPqNU0vOimm$w@Ir!mwkG6ex=E%`!5iW2N5<&o1pMUd-!`rH|cMT7`jU0s2AcyXJ zx?zixU$%!E#etzM=ax;3KX~o)CyqXUdsrx=7lyaC)%M(Lp^k~J)0^9FB#*rt>bNb; zZ`W^>$Z$|+sB-t2vt8@fo!W4|b1XEjZ|^swOc9E1AQb7DIQqnyciJBOZs5fD+v~ed ztt;w&_wC_Fc5j<2mgh|BmAPxkFwrnMJ<0gwG{v|ZqZ&8SwQO?MgXd1Xy>8;R@ISci z!s;jIj*0Hjn2Yb8SYBJ2njSrWUuezN1JMl3WM&ZOI9(g)9z|dRq&1aY+Sl*$-x#c;k_dwq@bT!Qr77_Jse} zE629A%{I^znsCoI$NHB0ZK2J3?{9fw4Q}p+r06cti(!T+^!mGbuJN8%PMgTJ!7de*CryAtYp1&5Bt=CU|u`Jf=4}N%h z{c9(~V(A&`ZM)v;G>e1d>vuoZwmfYfp9n46$~22erk}#~4!jrFKXL7$BoA0h`h^ zcuz9=g|K{l+`VOKi*65|+kI(h&HG_l=-OOviQKyPFMhj!SMTP>tA4>xb*vgV*xA{+ z`;ns4ot;TLjZM!@KF42(D>)B6x$VsO-lHF++LP}KbKBHY6_Dn;L&bSO7O5hlFk|Cf zeW=X9Kr|`>IEtq_rY^Imuw8%zVAN;qYELmIJ=v%q3CqIhq4S%T?kelUANK7VT6^-2 zu)M6<6&Bo{lb6>fHm;o*ZKS`>ORvBF{0EnlK6YJQ8=iRL{P6f>caCBUyG^UEbtEdS zI|imcV+`+G-}UWl-+cd4Xvy&~r;(Q+&JK(ZLd6D?MWc!dAq3X*VVJ2~nl7<7EaK)S zQ&%pnX93h$N{^TwNs-SB#b$H)&al+(TeH4%DK2zAxViVl@Ui~m&t{{kmZ!UFoJ=@T z7AZ3`*ft&K2ymyGV?Z=u=CGqIW?ly%vY&QcOqb$PTp==+LqA>?I%57O;JWT7Q zD4Lzz4<_IFC>j3ZwpIjk*Y+GT{}G1e{nQqZY&oJQ|JQ>%cD=jvt-C_z#xxL0R&T9rrkxUzFf^T>H`BOFp=6gP(X!`rDfRvs z-LMF5hw*SjhE60aEx*9S!w0owCW?vj1D|det6^7wp&c~tbq%azF zI!rlW;1+4ssr1KexLi0AwRx2Pm3~h8>9B+z`X55Pb8Ows(Ksg)!p?nf9|{*MiCq|F zG7@4(DA|u0jq42w6v1efY@m3^mZ~bZ`xk)d=G5d7J}ac3s=75Sp|*we=+j%eF5eR} zH<@*zWTlH~m7wkpCFcwfjY~HEhXE03-MSWT#BKmQG9SaP}u zqy3?zBMQSPiC_-=7Rt5*7>#=?`S@VCM62}Eid=eQ-wKFMO~y0Dx8-T&N5YcnUx>>t z2iLE;Eo5$1TSD3UxDkyKGm}7%R4VR7WRV70#JWNVUps(U0I9CWRo=OEoN^+ynRXP4 zQyfw%tPM+Mpv3{(gKux_37MPOU?}@Y7+{nbM2}nyW$)8eMvoFU7{Ub;BGM}HQZA*+ zl7IbAI5n8{6_Qp`DHWEk?0%qygR|bT*RmUJH@{V(tltgIiem#dS5iNKiAD*bUqV=C zj%b~0D7PYQlvi{fqK2MM(WvM| z4TUZsO{fb8kKh!Uv1$H5Us31#Wrx@O1dbU9AuO7H+~3}+fv`l5r%B{Tj%RiCTg6Kq zp=?mbY%p*I*7-vMx#R&|7(}s>IfRN06gfniV=r%qXhfPO3=DYvk;;0! zI?_8jW~KPjr-Vxe5F-XMq=(5G^_|I?+G8>iU2`r%A|( z(M*8k2>W}-j6g5S7yC6ATF>bF!-5(8Z2Q`^FKmA~)U~Z{kPqy?95T14uV%JZ!iXd!K-&kC zACT1WlKH90AFXHf!slT@2C1y>{qnjau+tmEd>(0=*Qz5~Ccl-f3D+5Mz?@l(j!@Ab zWndB1bcYx)z-X35bxd^RiaerdwLXDt|1g&*B%PopwH)Wgy?#q;tl`=-zqZx7gZ@FS z=cygTYeVK%w>DhoFfcRBF(E;iHjEjIM8c5x{YdVCWQEKav~)LARh^3t6sulT*QoSj1V&dZwxlmpBW~%+3(P z5Vkv%9AR-eCr{*ZhfnRn0ptMDaf;li!?ZFtX^jA!$SejRaCdt#pP%Z=-tzHUvi#o{ zn)GJF?@R9v)tqPg~9jh&Y%iGZa;BGtzOuOn^I!TW&Du9+ zYTT1ayaEyeqrxRs2B;0=P889MMG}c#`i5C0R3mcmNCV!TuErTvxe(3A2_3w6duj!Ex zV!=%0idOs#rbt~{SH3jU(s_D*@{iHbfYB4pcVT}wWHJrfGZtp}LJnd!7a#`%R}^)4 z`NN3#{;pzgIWuso7(|zwU3~lCuIrzYuLH+eDEcIqG=M*E<|d=QL&;R7%6WSFK>r}r zb=RddQ;qk8Or|k+ZVod=4CKsai0Br<0H+RFj2_*MKgN?fKm@GTTw-4Ny!6DZ{Fja) zVVGDT&d0eae_&uVU}qsYVr%I*JlMmW03z|2H+>M@h-^?{K13e1F}s3t{v z&gF_$9@+S+0VV)9!&opob0vcYkvhx(G2Y@$*d)+9!#v)6xGjx`cZN)+L0hW+hA+k)oTsM`^sV!x z^Al~U?9Rp{Wg50G%+^C{A{uUn0LAF*!j#RUI4*FRyUpl55m_waY_Z`~V!N;y0U_%6 zEqUOQKBFfmbD@?sPqn4dk@b$qH13V+$hTJF9FOYh%Z9fF~c1Li-r?Ds%vwyP5#)y6j?Ou$}L_KXHbK~-;DmroZQkLJ>MOwZD{_+ z>w8B=LMGFUdpbgucO#-DCc&Xl@fwt;q!~r8T*)Q+P;|^Ai^WeTFJr3_g+jiirTdFvw!Y?`qYidG6Ec~moe9;>J0Mar0C`uac={@4<{(3YjXR3zbj6C? zs!=P4xUpCc&6>?Nh~s9W9tZogy_JlfZ|@7UY-(%RL)Xq{J(@HBv3D;qc8+lz#~+sn zR`0iqHu?R+|Jh26Olm4jJFT`RS~{bn)eGa+GALT5D2k#k2^)%q2oYi95-U3kiAZcE zY(yd!7T6F=3v=G-In%kEIp@qw&lK+`dU}+!lP16UpXdMlpXZ&Q;WUfgfr3uSNQ6V+ z$KFT{H)k*?=NuLv_0%J_M_o<0$N>Gz8p^`H3)=iTEH+=bprBu{F-^6$XwQ{*-+DY% zD)Y1|&7yz)pQJ;EfZNifrz8krgEY`P@sPvua()1VGhHGA#L;PwPv3|TCTJn&fUoD@ zbuVw9>*>6nU)ewQ@a|Np%-{2AHV=oaOvf_9MtnGwCOwWIAd}I+*}P!OV90)+DY zll8oe`kjy^L~AB%?LuytnLaVIvDH9K@1`yHq)KIBUQ4qQfe;-E3p>c`(n;=C2LlnxF}-X?J@z#s*pOAKL#CUahT%^(n&mW<=h`5$U2vwl)|Z)?_cTl>_* z)r~QgMbn>VwH^>M9SSL6+tkCa2m&1IpDsgQhHP74$aGf#UNx=dK4xK%ELt!%zF__p zy*32%%x#t?LgCGTD~ z)d*w|n3lp2@Y}qDUWUDKasJWEX6d^P?@X1-q8d!Iy9yJM=$uB=RQte~TMnPJqcx~( zWE^C0rfX!sv21G3{SL5$oJC7v8+q-#gI?Yx+n5%-eP%I#G*v2#?09P81==KVRv2f4 z#g;V9A>?c})q^$u(fvMxZj&rBz_w*m?ZLE72nAQo^?-(PwVqr3Cdu07`MB|7UjAY+ zZm{X#YhS8V7Tc?-nTKebK_O(oJdvhZnK9fuc2T4M)qeodb%6nqZ=Kv$D_@5Ym^K9< z^3BZ$V#@oVYpI^d09!z$zx?vr6>~M^nYW3Y7kg6a`C_`NoL5eP*Hbg1T^3--#=0p@ zH-=!x>XR14TQbOWnK35A%4JiHehDbku54$~zr2|f1-$lJhPy23WkJ92`=aiyRN7ul zGn^`wMK_w7Ifr(c8sSX*a5zo34rN_?(sas$fJE12gaj`vo92j3<|uo=la>V%wFPyE|x;1Z1%8Pcw{S z2A4J5{~7{8Ms!=1uQwWS zMKcpyYt<6Ha>@;*IXsbRlLaEyrI{X8rnfQHzLyLLVU~d4rDZ2Hs`@b?WIAK=i*p6N zwmNTA^5#LmqnKf1s#F%&Q)waF$GLEl!)a7n6E7S)zCr?oPGF--}`1i>?DrjZ?B zR*sDiyRO;4kFK+TKmtX;n>~}IytHCuA3?`N&Kw`lbz}GJ!usP?>kINWgMMXERNLs4 zQ)_o>!IVr>25iJ5{O#AqovbSDz z=7Dsen8h`zQdvYJsf7bUAX_w(!5O?K&9rr|$ru}uSQ5e_1LB2R9bZwe=cNZR2+^U8 zd99pmExT?LgRVu#cBFw~Hk(tWvS`+&RyGGj2Gg9$-uFhDX-w3}rp6$;E(=fqgJr<$ z880uS?AePk?{nX`-Bxa4-A#Hd`0c0?kNIH2+<*lz2(|`dD&T?uU+ZJVpcb%N@a0epW5&V4kOcq z$i8KDn&HVufFP)ovmod)TSi|$J9S5`^!j6ljDoP}P-I5N3;Oqe6bo`FU02NR{Zy%R zXMtV<%$yFU8HRTokArLn1W3k+E|aC5eY3>8_i8LV5Shqy$n5{SSh!>9;-ZOYV}flMnFwuOx4 zYykq_^k=aM2GS)(6PLY|DwPF0l-krsU=SvQ1R!um;JqVhs@onjkm(Z1AwU)&6x4)} z2p_p>*+nzU`SsUQqa3tmV*!RBK!{s5Z!H$Z%5-_r%;oQGKOq0$T-UR_hrfVFOgb{!#J$CKJGT+nZT`zj?I3QD! zX(0g12BZw$wXIl0Po@5%xtFWwHB=Vn!PJqSRiaMR=||qdy3TLLq3ctP3eb0Od#aMfFO$ub9I%RZXuf#NO1gH3N@GG6+cswxzml z&jT4GAq+NMB7lstO@`Qg*f-nXlx+Vicz#S_OPj9d{tiAOB zLiOxs1tQZH0UWvel49}QoUSP5PWmIMQkkb$(xMrCiyCQ41yxHu(xQ+mCa)8!APN@ZTwr07n0o!TuE0Oo|8nc(%Kmlvl>e@ZVD zbGvLys#NCTtTf&ITe65R2YXXTUi^-Q3ug=hvcgCJGP04B5g=+pfKhXaTUTDYV-onD!bHEF88dms!*Po(y(dlh9C z%$Q7MBI!lTqe;K-K@c0iPz=Xn>Os1u*3}GOsIkM*B;*?uk`;J+((*(>V*Vb|)>%~wD4b#_LGngD~zfds;$6#+=b z`>q|jr09~7G_I3B?aDQ)cBD$Bl~q%_=3o%Q889%CS{icznVeY!5fWHn`@o%7-+JGg z8;{oqUgom)iDw^wem6k|FqCgIvaykZ00bHMO$EAb?=uat_dej|ZOJwt8sA)K&M)q?iB04To=Awf=ew5Fi^63Zo1H zA|Nu@fI=2680nT(cVAp|S5G;QTy*aCyNMz}^XayCs#*9-%Uj}BX9%WKnp0nkC9lZTWGqVx86boq8hLwd{mT2U8lBNtE3K%% zHuT{gdjk|8NzUU<+i7%Z;-8+miWnne%08)w**-x69ic{;d$Rh;~azn zK>+&U-gPe*ODEOz6$^6l>ivh#OqELAlj$Vy3K2|W4W;G=l%ZlXn6ERM@wf93J&{;B`H^QLs=GC(%dMQZAhA;3V;REP{YtNh{P zgTq&SQ4e-wf^Dv_AAbBb8Uk>3CLjP|2s)MnYz)SFX>a^>C94Y$1%85=-MFb-rJ8KV%GP8oy2 zcRsuEG^pfLgMMGJ$S!%h@6qS0b^HI)^XVi9v#ktf8oimC_!WUM5Qru+0(f(Ce`EbU z*;*^_dTCbAxAqP0B@D)41318dk&GljU|~9BgTFm=+l`kLro zrVSgm&j|Vy+c&ORH#T_h@f{FyhR7%*%=~2=VI%=$BMZPg2m3xPPGj}I-=aG%zj5@z zo2v5uAJ(5HjUh3OGDz@ZnoXY|Xon2fuYYId=CR?LZFdb1@7eU=%7KF$UfOx!3V}eP z9>mBPJ4-+Wrc(w2GBpA_5J29!YTeVP*@}FoG~uP9t1iD`&G>nN>e0;qJqM@mhKHF> z0b$Z?b^#bbv> zqf3zS%YXc<_O$d1XK_I>ho934L&Z}1VD+~BtG4XCB~||2-cJorFzc|osX3Zv_f=#C zglNTpj2iV?^%oFEHGr)QqGS+&nHK?w14qVsuPc_qpWk1weEEfC(3ej${%ge?wl>-( z#cAQvk=}KC1`d5zop=9NdMi!ZA+VXrHa*Qwy+H&7qP2ibKm-901XxHwJ@=+$K#|D? zXVEcZbj$ijM@PE);kQip^5x^%9{(|(@$r`nO3SC4JLucf3G0ixThDoH{i^+at6yH) zwZ}C#Jo(C=!FxYEdPS=ITMVU!?Gl8<3C_sbL>S&nP4vpR9xO=?7MTzrz+{0zaZ;go zhHNlGm1ObXL(vQe9E^bgDb!?Ja}19bS7)vBWO^;Ek(BpLuZL zrib=b!*-{$gK5&53>nejFpj|Fr&3cp1&BZy8_X#nQ6uLmy+2_}LWBaO%wsogexj`6 z-L|||!`oFW_Qf1tPjxpGbvx5^x7=~>wZm&}xU@J8T>kRN>L*8sH?92SrsqG~QEkkh zPR>h{p2R>llcbOXHmBy^KnO&TGawe9sBvJh1%zZK$kb%mx8c!ORxeqVyLkKE_g&k& z{KDn!m-J!|Yg65eMcv`l)N5}ZeDJO{H!gXNKglK6^$hhM9vvRryz=oQTi)FJ$SqaX zUm|1aBwIn5piX2Nknc)!SqB8hfif1Sgh0p=fgpnwssSH3G&tP5RAa1{kF0rS<(8vg zLWZ3eUf6cd=kB_1Om$m|hNG#aGvEFAqth+&C%O32txvD*@4dh8uHk1lJ-YIpt2b=e z_0S{xUOhimPMO_lildf;27|NA54~w_+i)fTq-?Rsf&c;pqD}y}y!6SYC)O^}=hjPa ztX)NU00$VzfEj5!=oiktM(>}D?v(Vm@64kgKmOn=Prh-QMt(L|UV3Ry&&b-fSM}cA z_r$j0;r*NTtXw&G_0>0xe>>Q<_mM~LxuR+S{=?q={Zw7YaUB2jyLI(_o;BLu@4@?X z>+(1T#})(_AOiwP#0*mslS&PRv@jJlQ>jgFD)XW_TWfXGkM%FKwYF|*tJPXtTkERU zZGS-LdpJMgeDNfwICQ=baC)JeBCqfJ^Yi_^1qWS;Gk}?qB_`tGS$9)U95&2Qz@xyw z6?GI9kM4N(A}Isz`$TjOJ{%oaL-GbC&F8A#!rOkSEu%a zBIJ2!JMAj5~y`P1#^YGVEF-}Y$wd2qFBYi6c!bYQ6ERR8ct<8!;~~`^=?_eMh%UT)kXF?Kb{1t(W(A zwI57vc|CpKBGlI(n(qm{G#)xWH#c{3rmd~*F1KFXyX*R;;TtW_oKH1h{cPh3Rci>9 z{Dk}P2d+Y|1fkLAKOJuaVUZw3=h}s~!`I*LneS{Fm>hXNbvl(A8+mPCU(aC?3_(zD zD29p`IrL=rseR|)Z{PW7ur?mL@W|eQ&hD9?g$fDgfx*Ux#Urm3Y_I(lL_6a0o?g1Y zUVU#JNI&||j!kr3csPia*V@{8Ihoww6)qt(pGl=s)6?k%#et#BoZ(_mPxtuv)Z9$? zWN`n!yW4ipO{S4l=;$-SH}~{ zM}sx>=;*}s!o}ljv>>E74ccORa(~`~t-rh@oIg z$Qy`&saLRXsFZp_N17VLa(&aFhfjkb+K?;Q5iIe%s|ok+IM6+x&V9Px@BAJ=5h0K_m*Q~?D+P4HAo#RTkVI_2SV+|3u`R;=iquE?@!+4gZ) zgW6`MK6&BPiGh*5XWF0GUKbm9vD)$BwvN|_F24O1f(c$hML`u11QhZsZwesZJc#L5+YzI0n-nIr3=7sPG5KEF2H-!gc1%lpl}quX2K(SEGjHtugv z_eSaY{Of!#I&Y8-vMHv;Z&5WXw75`@;>{4Rpj1i#L_cZ_2kw;wy|HnH)rcNfS<@r*xKM(eq)V@Lb; zJlQ4)phor2+6yQi48agWrYy6gF|)l^Nkwl6qJ8d)MO6NW#&)^?tvlKd=kh-}J$8Dc zd0SUjS;-0Vig$|CjsxHDJ?Y zPi);gIdr4@`XLcaM3HRe6vR}Dn!+jw9#Fvxg5o7RcK1q^2<({5q`Ty;&iepFS?1fY znpaTKGlf5h>U3pkh$`w4L&q;$=VzcZ2a(v_9N2+ zee>NULP%-{Z%hSDi|bkJ9tq_?;w7UE_1Sm<2vuX*_L zx!!}R>DLxEU+kVbJPVkh2wAEaq97QB3XUSEkY%8NfL9P8pYVP>VSaTnbZZv+KnE>^cjgFLtBR+q*v@9b)RWo+b`6Q^E&_Q2sc5kQ1o0Y)i# zOPiTyH@OwT17={BPdmj;pA|vWw2&cGKx=o^-3$e|T5c;{SSC}cD>^U}(PJiJbRHDE z<6`w|{){W?cy;3omc%={`Pp=*!v-+**5g`Cj@Kz{oNbk3QW{q404K zeNpwKTWV*Jx6#Gw+M(B0O!{L-_KqAKJkdGdb7^XB_pGWK;>nZR9zd_LGK2TMAQT8d zcSD6T-6?(yBHZhi+7cA}k8h#JR%L#B5L{?eHIytPL%{%}wok3p0A051z85fZR8(Q8 z@`|1oL~+Y>1_gUutiBy_k&H>0wQ=7!N^fm;;cvN%w1{8?X`^|7+EF&`hLYh z57+F12*0>xt_KB!E>_?6teA5|3?Z9x0Z~8z5e%*Q59y{O^?y-P1+= zCP&?)+DUb|Sbf{=B8;|y0H#Pb+X#Rs*CZA7)bPQTwPxZ65~UVE$PpCLZ<{I(deaI` z;~sHtrPa9YE*2|i`-Hc{#Z2qm<;q4J^o3Ul;V+;V$^ z0vD@uSIg&o7Qpb{0AZ1W36c%E0%%;(SJIIvaU8+O@*ptT*ifl(A3f^wz_eSgrKCq} ztiJV@pP!>~fUDqCqR55~U*R#b6a_g~dCg|?Mm zUsFYuEcK9{qE{#An_U@k?}=Dmd%2t=ane^!KxKcTNIM`#mMEeZ8>CFrlZGCJbk6R}9iHwDbcL0>Jy#0C>Zdej?v z3dpZj3i{oyjAKE;tc%sNx0coO`w+~>Bljv0>|&zGmz3_Kqld#5Qq>5$E2lds_{7EP z*}N;2ybl!ttM&E{>w@o*)FaB6~SPps7WMV0U=A_ z)IXV`H#S7Rz+qFw!2D>@Z*j3Y*6fPx{MJM~w@O6K1AKMAU$+$sfca3$_Xktfe4@`h1B$?^6@V_M}y_b}{1 zc~cSpzd^yLE>^duUBOcrcucuwA)oxd?4ZuLTWddI5NSsFE^EMzDGMJyDKaYqv5c1la{z*m8w2J*DD5s%< zcXVuXv3m82%X>soL|FwzRCqFxNEi|a9WarxM9!0nU=+xgVK?`Ga;YU0l=GX5FdcV- zjkzh8w>6to5M4#k!MBGmBms?kObG_(7KiQ+Bz6KQsF1Tc+%T&==!@@Mt)Rb5M1#na zE>@q;xts|>ST#j?H<8w-fM*FIIDvaU4*Hi;A0o7O9L*aLjYx!)T10pq>YK9ETf`=8Q>KqigG@Y(XodN zQ31d1lI~HxmTftac;G7~N60uC?u@5vdKH5HjEfQ!Tye2_^q$K(U8PqSDjdpuSML-| zSV~pEqc>|Gs|N~u&s*kHMAqkw-vj${Jtxjc$=5;D^ZMekxW+03{f3M3SdjOGi`Ao@ zE^DkxIr11Ii{n%&8)VO-ufdxcQFi3UyP@W5x*lGkT8`p-6Is`UJXJ#JL3{XFg`od3 z?xHluXXENz%4J;vL=0h-1ovh0{Z&)?MAKS24imw|MwX^(mA~Y;SQDje| zP}Da(kT};<%WqGO_0-(xj3CDA- zR0I*0Q4`61^KWJ)eTfaZjAVYk`mC!cwY$3?&xJ{sIvckDp9r6vEznoOMQ#{MQ39B})kngy-DS zpN&EDnYkd%HoMFsscAMn)&eaJ=IANP%0r*0wU4~9uE)62aD10_p5h_$AX7A2^<2uVYjvw}2n(ruzrQf6Ed2cP( z$TD$FyfkEU60*?5+4r3+%^uQh%~G00f*T;@qKaA}1UD4atKNf9B@ikSAV3H%kPs3Q zH~a_88PAz>&UlVJV~@cEe_YSFjXj>Y{>}65=d*vS#C34dU{5u*?dyrZYHV=gYX?W7 z2|udEyGAI0feB$wt4x4n_v>LgR9yJETCRH=u%g!vw$?`W65W-zJAX|MO1b3xx36}wzYy%H^$hyGD7$325- zX2)if^0D|Xz8~x9NycQp1}0JgOEN~lXqAG2gDOF+RnE%@r-4u~xR8#h_x)5AG;#p( zi>>7*4uN9+Wh_{<9WVE*IeWqqYULL>q%K=w=y7%egg2)~qesy9e9A!3;}^sr2# z2@k;O_v$O;GShAEjhaA#>6x!in*$l$NnJnkrXyM;&`ag58v#VIAY1^4C%#E9B6x6W z1pfEM9c;QEiKeipl<|>h!MyU1G}H0jV7-#}%&4SS{^?0_ekYo+8|3f-b+y8LaJYOC z0xh#(;L>m!r+$RfNU{LhTMKRc&v_S>Q%>aC-M#x{Vp+_6W6hzk3>^>A=jB-FduxTNi~ie-bp>5sh&9K6ShPX z-ZZ1iET7nSDs>KbEYKnY!*n{6$aOSfGIyNZ%-IjkXe0*+2KUO%?L9#&E|j~H;$7(b zqE)8#-Q`%{&t({e0toshK6$f&5sXA2oR4>$b}Z=Uqp9pIrS3!v=7T@?3?^ke4rcW( z(Nq1i!8Y@rJY3R*uN@1#?F>NU8D;_3&3Kn7Mtcl6nyHmn3kx0axSaJqg{)yQYMN&?iz(!Cy9P`kr;!EV zL>kQoYjy!9%rF4aKrqm32rfl&KjVAf*EXT64jamZ^{5E2yhyh;m3v=TLfK`A8C^Vt>j!vAL}6jzzIwuVSlPk zK>RwJe;f>sERA(6=+TsZn7_L6f(}@|?cVOIkH6Jc(0lSt?71>n>7~CERekr5fJBBl zhS|xku6U3bhhWxDA6+cV<_BX6Kgv&N?*Y+72;gujU(j>G?Yn{qIFi#ufHRl(*1x3r>wiEqF)X6{&>|3%pJu7a`gZ}CK6SjQJbS$A zjhp=&PHtH?vgW1jKlwtb^y1z9{ZBs?O)E3bRxmu?IqBT{!QQB9HF6o2t2D^qWI1*M z;i0{8cQa*kK26Xs<{RUy4S_@h1LQDJp2F%AA|L~&kp~MBaAjc2nyp1I7u%kUtduz| z5PQ1Q7l?C61-t5;@LoAKL`De2^kiwQV?iH|ruM~B>KoC5=9cfgcICmU+duAqxc>et z%SQS(Kk5DQmr9d-Y3Tl?8}(d`OW^RGd8;@CgSdVrowJS7)FCY5DIvIxlwkEZO1;4AkZifb8sw3>^y#A-&_6t zm)F;999{Y5FA!J&#B>Zeka;~D!sH6gx-xdD)>?6to1FlEu(-1o{eCpH&83WO(f_%R z9=vw!>89J&530KXHLtU+%!NRqN%{va%`i^8Wzxsp?J$$2M6OWMN|8JDWgC7zwx2d*N#_e^l1Ol z!*PjzX=`ti=r(Ybcd1Ud+|%!v{4$IKL6}4;1QU0< zs$0O1Lckp(&>}PO>)asMhF}yED(4M(0MRJmm|4KnjWDU)e_HW&)8{1*fTfi-m_P!M z3sieEF+Bq~m#qKmj+;LMr-7mHp!&nyEE9Dm%#CMDmUOnFUyG*JpKs?sr#`gp-KU$@ zt=(|)o45Kl-~GlHOC9jS)^q3A?Mg?_;+s|y-WcbxCd*^uc7Ml>ZHOjp5yAi?6*9Bn z+m$xuTW03@n!Cy{rgAuKd{_Ml$7tjVW;lkA7KFUJezW>44FZIKfMfxV3#h#dgu8)& z7N--<_*OaV9M-fY(pQdMgn&~c5rC0+t<2I6u8fPOaUyr!)lWswi4Sc%w(|DChWq2M zt=|6S7fT)W#qWM^{kr47OM{sNDSjVI{@c9KGZ^1gp4atg!t1!qNSI^k_-njZt#sneS6a<0|6Qc{=6R%Rs zsB)2V&eVoAAy~d6p`A%3?^Mvg8%^V2!-28?yFRt^_^O)^?~I>%@{JctbK(cX;~Q=t z`&*ow;^G~LYCh$>@+8ONF~8+oqX`=&GZQt!iT^#MLuvPMMEky7&Ovwx6f%a(Io;QT zQ7BjtZxts!^Czzy4+!e-2+3X9u$v>9(JseS!FRLIqx<^;Vdm-MixuUHqb7tzjfm~< zbkM&aO(z$M*P{P1AHDa^TZbNv3~&EJDb@La??1Zk@t$gE;(x|!Lv580GMOGsSF{cG zRQ8O|6Se2JJF0mL2h7NlfhSwUUq;M;*@kK>JTV-MY5cI8Q;>;l z10BFGJPi^iK>!(T6JTn1RqMWw;SzOb?^jpuxeOCdO#p1q(xn{^`aqN_TRTQt8^H=z$i zHLH{)J0SuHzLWgUJ~d`S^p>;kAU9D+z}|A+YZfRFKsekk=M~rWy^r45ljIy6=gp1r zF!k+&0`;Wwy*!r z$lf^KbLa<~uits~?D5m}a@bQ*jdssO*&ZBAOXb1uTzYu_-0;@nkrjs@{`kbT_|xZC z!@WAL|IMi8JBCRFjD*|(?j<8z-^VP_KD$xQ*@tjZNVu*x?p3)!kw5?iINL(TQ`@iK zzdX8b-MWF-$5&^LpF4Lo0LW>VIW7d;D(7qi!%2ituGk4O^2DB{9S?dmt&OFup=cgD z^Umn&`-gw>`O+ME@#UfY=Lc3j`(498R=OfJZjbk51C$N);9%Mzu9`PZ_sUcG^XjPP zZ6M%AhM5f8nPg-uW{9>K>+^G67Zt!n0mDICyYu-xXB1L^AUKh3Nh!{Gs_1i9JOIaO zmkBTst82Xn*Yp8%leDvw9OnL!#R?Byzk|IY`x{K z$2sq8&|i(F_d@G!fVsEl>c;WCb2B^k!q>iWMz`V_5lL&L016jrFv}67&WFK5MT}hf zZcNRwKR#*NJ0~VQP-lN!d49M&r9+N`p%7?`>07_yw}T^X@k4$2yCYvc^y3p32AfPNKmQdyIUTO&Y?Syr9|R9% z3BVw1E+_h_3tqc3Fi0Y6NamPwOJ@V&+C*9F?w)8A_aYZ&#uQ}YLRUjt*O~%TFr)xO zfGV*`8Iv`>%RpAM#};Svp?o$2)~Ac%auG2nHqP|`OeBn+|mv= z-TxW2;BrF?_dj_U?6c9YzSO~n`WNr6S+Ra})w5qMO63Q}tMBt_sI2qSzuhZQ;uT`v z81n$Ix+`gW4iQKR2w-Nqw(j-uk&$JG2aW>7FBFc>LMwh~e0W|*4{0|RhdwdLh(5522)H*qS|onOenX`RfG+zzDAzpEdFIk*f3 zNlwCynEt*rHv6Erz0hq_DQ`pcAMv<<+4j$ODBoLO+p_WO@jba;^`3Z8tY=ctlWDW( zx2VVK_Nvo-%lAf!^#aVvZ~&LGhaq1yGJ=8w;hpg$!EqrR>i-48$Wnb&tvmUpzlwv* zC}PL~hy8KZG9F<}OYqgMNlo{cag-i-6OaQ&HTA*R^Xlftf?kj5;XfG|Ei(lO;2XQM zi-d3kgj^C>?%aFCjm3-Oir(2i`sJtv+Zq>yMRV-KD=&}yq!ZomuMdr{zj1Hi562$Z~*7cPC9#pL$A;1A7`;#Q~6G z6p__(;Hw%3$_^SPiYU0iko8ZVK_3AOK#hAt362539_=fdT0i6#XkADGd~a*=dU4J% z+yNX6Z#a1qbSlnyrw8MH7`39Yu8u}?=ib1wug`w+d-eVem#$o_EjCXwJs9^)lXiJp z+a_&4nLo?W=SFQA{{5)#4TOxCL`ssaNsHPmm_$MfLaX}H!k3A0KTg68P=Edq!I6}+HdEEBaremK-VN^+t2z6Iagy-!Q>Oh(wpahG;T0%+Z@~*)vZiwn&Z^Tg2btE-Byt$(EHbuXg4f9c}ktk95h_c zSu0~YG6MsJlMSO|+mKNpfNbI&UE80V7LV-*5QYg5n7}=$z$P&GMgNZEGBNqK|0Y^y z021K#+rtyzJS4STm?Lrw7)th`&pPPs&xwze8vHoww0i^lXH&>uJo4JfAMgG{C7{oH zHJ;_7_T4lGTFO}wwdD0uY|aje z+~t>(u#8Grb!5bfe?ht z<40*16{O?_A)bFS+a5fehR z%&^lp#}S1L3ok96wYcT~uYAk>X4I0~r9{Wq!R~svW~L$k`hoMeZ&U`42je~8wFRd0 zYYqAQGqK8k&yGyDY`M!YxsxRs*x_>OIv*tarsX)|d)eM=zU{sNL?U2j*ULHUFUro~G-kn_b$OF2uf zMJ;)^6zllF`;GnsGdr8@D}8s~+Ev*=GB%bQIqO*ndJAp$p24$GYV)4})R=Niz{AY& z+GYtCI1~3UR4%s51-UGh^WF}$%FThF@6I=(o$0ttn2E4%@@BOy0On?4s^A$Iymh2J z$>uXi;6b8A9uSyOAOPV=Wx#!wD&u%de_zU38nviYa4^lx?Hs-Sg_-ol_kMNs%0y+V zWjub*On*P$!)UWpyWVQ{f88lSYBqlk+{|Xd%S|1K5H{X7uoYRYFogCvv zKC5q7Yr91a!SVOXjdq*~a>pnZOyD3keK+$G(>>7-SPvdBS|$eqVXCgi%{b>BNO~!6 zC~8rYjiAS8GxD}<20Ke%*t})kwXw9jS=}|3k9E)d*%3BBRenA@??$O{k`g0#QigrN zrraQTAP7ldxZ}f9?F9L%gGjhScI`@cwdEZ|i-H9pgq69Vk1!nxAP5uh*Pd=Rt{aIN zg@g-CX2yx(a-$#Gyu|qGmJ8L$4=k}TE zN|yAhC22JuOe*_AC%s7e>bNe-IBx(Z8i^0=X;-d;?&*?LfKyYR>+l`9lhZJMeZy&o zkc`pp?28N2aU`#O0Ee42zjqo~pnzdWLC6hk*R8I`zviRhgg~o2U|oUL&8(avftDhPeos1i>~nIc8=9 zNE8WRArMR=Sm5IN+64GMU*|^vO;4s8s`{CQd{%ap{W<+sKN2 zsqfI^g?aG~NIE!Yjzk$>b8s+H7UU)eeLG)(g8&6EFnBddY^F7yai0b{+BoJ8Mw?`8 z%na=J)22Ol)75*4-lxIYG#~AUMuUxg6xi{YpeTGZ$vuHub#QmPpDC@DSF>VHOf`WeDoBT4zw>NQPf(f_eEQlh< z5DFv$+FH)J#tgWy(X=3Mp-r-7`FdWB{jK*=5Wx_^t9JzgVA|avAtWaNQkauuf>aG0Trh_4* zCWjj4uc)#NGr%o?q_8{}^X~e6Vsj7~0Ua#o^vaNCkO#D~p3o86By6DBX^;bwzyPbh z6-hp2`N`au_wEeLO2C8%*uONc#vKUy;!@tGsAbLPyAQNEr8ZpD5r3iY=)Rqc{x6S! zn0td`aXEjxH_GTeO_ih2urk{X^=0#L7u<2cC3#?PJt^Hy&rv<;WJ#>ew*ZbBftj&; z$%!ooXj2IAY;(1H-JN^#d+!ktW-`DjWPyNN;Ov_0X_8k1&ddpfxo#}2?q->T*|iTx zE!$gaYDLtBqXmQQd)MzylAST{&P4v-6>sHPoB!YWQ*cMh_R#|%fis?)vfh)s5Q^AR z&Ukl2TG;Sw|Zm<^+I0^TB$v2f&2nrD?Q)aE zy6k*@-2hl{IZm-4sssf8;jM`Wsr=RU1w+h=J+7vFY2|H}CT(526}9eRslchICGYHL zIzRsCM)m}=!TPw-SkK_|f1uNU%Gjc3TcfO7GH^h`P?|>*z-YE9-hEUcml+N+|6{h@ z-t~;Y+J@HqC_-eNE$18x3NydlQ84{{S>>Dc(8t%cKiYATpo{2*u z7Q#MjuSPj{kRdP#0g*6p#MRt|3=SY%jsSwhNOlzNI)3+Bw=SWo?^oQ6i%#;K$9EO%PW%Ve0a+uhkl@3%#Ow{k=9 z<23U3Dvy^I{nZ9$ff+L#E-+IZwG@K6XpJ#b0tS|JEa*#14Wf4RPPtBX z+3A8}zU+%l8vz>^Etos$7QV5b(J1e+i~@2{en;lnASdPiy?AaaNp-Ia3>ePLSM`bp z$$4qX=lnKcU^5RY0^!V%xeTuKHaow)tNhT-FaUQWU`)qd%#yKqY3C|>DevW|9V62$ zNnf)rYVgsoeXrn1iIK{wjSHRcITwe0yrBnn%_$~qI5#nVSVo4kvfP#V^9LA?q4VX` zMkX1`MymUOaJk8tsUUD>Odc?Y^Xa9Ma@LBN<>%FlNjQkv*pLpkcRc6^qIR60eyjM< zo!b|xt70$SduQYIACwAwW7VPsb7?R>GOBtF_R~9}obAg4IE8#!uEZS(j*+3w*>xhz zFeD7tWlH1EnWUU^)(mFT^i~kF6Tm@lf4AV8${PVA)?(1Xm<9W3ydbVqmdb)xj6&;^5?8Hqcg;LoJKYcG%HMC02VxJ>j9d?a1}pQ2 zu9@6tH5w;j#;mvyppDJWu#H^?nY9*l3>27+M~j!tT_1g2)Q(-Hj`-5P>aXI$nzuQW z?B+cht%iuaVi;ULU{aUqS!Qzm)Ton4}`Om4ygvt`VX0}@rsUz?Ql zTJ~SR2ofzY;YebJJe;nyx96_uzAI`=sS}2N)l-}F8JtJg2Y(J#ez<*6-WCLAG73B{ z=Ug!yAW)+<*-f6e2u3gjVQ0P4_nJEmdUNIS1UR!4Aefjrfh>RrR=%7|c&cmv4S}06 z4FLgTrkq<#mR8JF(4+Qzr8Il@7nZNjjdV6GIZkg+Zwf>FdE@KUbXdJ0kJ#13Uw#XK@MYCJulbp;@5Ty8X8y!e@z zZ(Q2-Qqq)}JTQ0bI|dd&0vs^|=1k(qqCk*Yensfk z_a_y6f0XUPUJy>5uBNXK7zsnju9UMjIP;ECHIQ84y_r*QU?4e0+hiD%$sB{TOC~m_ zE}5gCKaJY6yfhob3%gGLQ%-N6G<_ZuMS>V8=WKIwgmOb_qlgS8U;dWd5H${lUN2|u z#O7ZoUASY$p~hLZ+)*^k-fj*@h&ev+ag9lQwV7b+2dllwi&;^Kqu=V8z-QDS3 zcX@mhV6@9Ia2g2%2I7#%=BlDc?fPzM#`Z3%S{QWyH+y#$Tj?0bar{l}ZoJ=Dqsi~D z{Xa%rG&R!Ls%4B})H0T_%wX*M&e->TO}JrjAwopBvBZ_QA&3wnB=+^fdO;+(b>qD6 zS>E?Or*B(r&oMZknQ2E?)12RV_W#pW*#Nzx2KNyd0vA>Mx^j>8L|kTpmVg!OvL7xC zQLuZGxfwHenhfD@mRiUIwGnpRaRU&TI3$_YV5<**{X6_k?wYu5pQ`GwwU&H19i$I+ zDd-PJf7kADW|uwVzw4HME8boCwda>bZC2q_Fx)__KRzkil{SVnS%~YIq+~QW57&e6 zASR4lfIA0^09rt$zhU{nHpZ@dLy!=x%9EXkylBm7E1X~eBH-TmM91^ecESRNp}-^Q zwX=uOE|?iBYC&IQ^Q$A3J2lSiyy5Tm;qQBQbi6+Y*FEF;>s>o4^#xJI5rC~piVhZ? zRt)9~%%=k7JgdGiwGxA3kR|7&q+lg%th4T!fCOB>v^Y%972dkbplDUp<}8d#j?{6B z+Rb#3{FeM-zfxexY4P*xjo$taLZywWI=a{%J|0XPZ_35*q!`LWu(U2n7W;=Awq z(hNWdBt%0XHF4GQbCQNv=-|lGRkf}L{m$s`x*)ErC<$9-mld_g;CR14d)MUs_Vy?5 zrE9^ADhB3uZs{?65l%Y`2jxXmkNye-4uJ+gEUQLwlH{g<(Ij}VG|v6VHs&ThfD0+e z&~;Ch@;z8Naz9}CsaM_lxdcLhuweGVqX*qKS^Bt_XyTS3lMphSFan}J@e$`ErmB1hAo|p1JX&jt_D5&`&P%9O{3`gc+PH0 zyX@or!z|sB=ls;D?4|&LV1a6uHpaOdn2z?yAJKOsVFHYny_3|NW=L){3F(H?^1IyR z8|`Eq0U$_#A#=`LK3miW^YeVgaF28rj=wC>>FRW|%tjaVo8ruae`l%f(6q=z|7NAG z$vf`{Mr9Wv!$DNbOTIR9?n1PCsrBF<6NUgxv}Etwoq$Pl3X~LmATu`JoI?N*Az&5& z6BnMB{d}rSlk>ddZNcENOv6s9_1wGLq8|`t=2K1Tv3z1~R=aFyZNeV!ci+1t>ndH= zv8*OT80J*)Fj30+{eU6S=9*R$b&li&Bl{y79?jl+mIE#j4Kj`{KH?8>IUd+p2|^G7 z0}*6o-a7Dr_8gZR>rzL3aIo>tk@9Tb)dbr4`k~=&x9G>r=VtT5wdkkEb8^~1Uzg+q zqBQ6m+LzS~s}4A3?uL#nK1>g$lfWr#nFCp%LDXH7KEjV;8d_Ei$AMy_ZqKWjK3KN0 zoa&Lh%d@6y0jG+CAvkFy$*m9_ z9D%hS%jXG12;>Y!_DTMxT`|mz5rC2b3M$q=lT6}EL}C;c)4$RaRf|;ITcJ0SXS(U-i;GzCzpMhG@VHh zG6F3QZWm<-TMf~s?eyzfui1#Vx>u4RgexGxSbNnaV{OCnYWJ)+FmNk9(Sn^*OL@0j z^lzg1xGhetEZ>-3?c9E^y{ks+Ga#dr<poW&se^e1rS?XZE=5yrs#^)~xy*6lB5!4HAy* zfuSA*ePuKs*T<>T=UC`{=lA+mox5vvAYHUPxxB0vz`*6qK#vtCkZ%x(P9}dVX*k(1 z3*|fqay$1TG_>iN~&7cF6rVoOi&QX6Rp9 zy6@!4yWf2GWzSstQSlz;K+aG&COe?IU3a_g7e(`NMLob#-I{o2N7RcCPxZU*-IBHF zHTq|DsqPxh$5wG>vnaDHp4r-gzAnh8 z;ZoyIm6z4rsAfWj!|SUHb|ctBO!#u$@zxOWN&70N~6yCSdh$^hTUCz_mcoBEk!vdD{d&c{9o5&?;0a; zLb~;J@snQrwSYec7sf^VbwIWSHgocH9b zoz$O%>ymsa{12co*H+{KVv%0T~-=e3hbwhE%@@Rh6$Eh_@!IXq)R1y#X9!YYCQ^f$YTkFpWhZ&W!B2v%M!N9n&c%*ZpA9TP#fnA5v zt5dr&8JAAw7bo3cjOOW>e0?o-Yg}cIfxg9;PjH@5T2?cxh6+M}AUvAn4xma1n6R-V zwcMy;I$`?#Ho+a^qNL#8S#n~LnFow3hf?Wyy6Zt7+9aB%YvRGey+Ew=n8x-!b0Xn+H=%*BcItCM_HTh0suveWDHb;78Wj^eKporLRgLM-`_`z%dIT8C4v3{n(^sZ;(+<$c}GzxIt7{lTOOhNu&mo0{Umy0DCB1 zSWCCH)C~0?=o_MW+ape09+Hu*_x=F* z^+~}ksFyhp23QC(M+6*U{9`0A)A>uYNw_WseYh>?yF{4<6qz|PYT3V{((^#y;#*Pb zT=BjUZefC1U{s0Rv{f(1Is?TK_=?;Js(odL%r1(WnxAY~>)$Urjw zF##hymZmWKy!x#30|!*GpdH;S1}8}z`^q~K!FYbFc?;0PCie~g0m-!2`Wci{@A za@W0dre!i(fR%A-|3bZkcv{B|^etl7J#|kh=KzMpj07L8$znd(oNo_QFdBBR&$wsI zXlD{^NJg?Qxd+k7K2M5H5{TZBXbn2tK!A*Yij2GK?Rh=w-IAS&Tj(Lx@_JgnKvz{G za`%D0MV)S+x+yAn+~hV(lkOn*cgpkaAZB84T>W~H2AjUM+&8JY#=z+y!jZ{}9J+N&}w8u&XV4x@!0B!VanCo^7uSzrNx?`VRwri)Z}Zby30o0t{y+ z7QiM}7jmvyf^L8~JV}y=!~y6pW;uf zZtRlDX3V-5^z=LY9_Q|i<|*Iwx?hxgIG)yRQv-dAc8cYK!%N4{B`|!J_?U6RNlA8D zcN)tBkc?_3IdEb9{v{{iIMcGMo3PF;IHHXk>|GAJaC*lKI;a8|Ssapr`m<&bzMxi}E$8ApEf#Un}XdKtYF3^2DKfQgmc^+q-} zh>O-o3vpk$S5SSh<=Dn0{b|rg``0JW!|3=__A?y4Bq~@sVG>AG3Fd;}c5C4R-=mCK z1zEk*{fQAm)hvDxV@c{kcVx74?1Chh&kgj5WVdJk=)9QDazxLV0S-9E^Jz=o8$)dk z`kv8(oSfh3G)pnqYik)F?KkHODl?H?$g;9j%;2mfclzth#t86Wnu}|Y zsd}B=?oLw2ifOsHEGbzUf{R`eCnfFf!RZ-+nS(KLUM+pSQ?3lTI$DrR>dkeO+dZyu z?o_sQb9|tGP5OL{*SEbsQ5v3)84@_o3>;wpV$|MYPP8(8QV3SH4+e>_zFr>coZ!xM zoFlT;yd1sc>yu9VOZ14Dg5!ci(jBioYtd^x_fHpmlWOkHI8!K6b7Nd(_jnJO1C`N@3${(!u{NGDK9Vu z05OAr*=?=S`QXUW=Q|j)a94I6`dG!s3WP~_HNG%_(a9h!|5~&Gb9zbKdw=VHB71cs zy%G>`-PmxwmQ3%0KJ;l^ba%8cyTqx}qTEAqrM)i8X5;#Ear0YNJ7wRf=s5$&X6d;b z82gMQxeLHBW+6B?ePVb5fjgZ{2v#SVeK1S`(c!wZ<6g81jNUN-u`0g;J{dV6&@1Mw zeKM>6n68@lOg`tH*Lq%vqvF)MD0fCYgNGj3=KEUM`!(d|u^aBjsA%b05rQFRX42=S zzVn&jh}i_LNF#AAf*`Ak&uNY9?178vn4VACTq}$ok~t>hwj}o^yrHvqZ|~u( zdt7p6v@pkX9DzGCp3&Hbfzh-@pPCw-DqSEp+A4#V`@e6q8L#~wAFZkVW%sCPC3CiR zbr!|UU~(nG!Kfetn1PnooBt{Xcx%b-Ix3x;w7Eb=VD2mkGZ{FT%Y++qW*|~wMbdI2 zi%b|TA!ILPaOp|gb@e=ITA-#lB&gq(oW9;AF4{6$m|f${T~ThocxJ0l{di!s-lk{q z&P}!6G+JMEAOD|6J^K-4KbW(Qii(b4HV(+mIja%!`m2KgWbRZk!`y-{Nk8Fass+JO z&5IlUlCQR&&8+1gv-nU zjJcC=nVEv4i<{GNC$sp3g5_s-VcD8=Y+kh*`-5(rtOj8x_@&fIzi?@T1a1 ztfQI}?rn_;ZWmS7CS@bFPZ45{3|zw9&P_l0QF9q)%#}i3FsHD0=O`ANfp9^{esSyl zY2MNl*237$uKRG-U&wQw{;It>E?F5Z)O{U2gA3xhTYd8K(@tG>;v*Y29H07nV6@eX z|G(!vO?v$&u`DV%;2q2YvoXUypDgb($8ZV2sN^6Wlcb)fTDJVwbQM)}V%CP1J!kx# zpd;|-voEs_Ts6jA2?+!c?6Uld_C+RAA{d}|c3qPV%!jj}ck?)Emh;jWyKvXhuQ_^xYRqWtaec&D9~EZK3}h9x^~yW>tf?zG*XamgXkg5<~5&MEDJU5KZTdG^-+F4;^i z>QjeAWmlO27_-u{WMr_lcsw%l()s}40;8G>ol<{(SdOR?xVtbapxVhJ(=_QT0&`|U z_$`IOb&!d$liT=(|jx|5mMV{E87`JJY`+big`@33Ual3KJIUfyv@E$cgN_gyVY ze=NMHdq+LUHBs)Mc%k-OvwV*;YxD2__c{0Rvrdmnj$JEM6PVo%N!f8mFw7N(A+4(2XFDJ7Z0f zPQPVMdBb+yE%#dc20QM!Xf){^QdJaUKG15z4na5 zjvmdI*v9`4{rJ&BVYtzAqq3hgd%Fd=(AB3WWj6xBLGVm5}00$>WxW} z5#*S>5qB^LQyysL`A&gUXuNTgWEMbr)VW(Qm zm+Z9U?Y5vt3%7UY>6mjn7t?OK{osR-xOm-u@%$XR&*dvVD`eq6d^l#b(W+lM8I_G4 z2cn7qSpd25!C2B}3>ii>Cllbzq~UTfItj#B>xjrmXQ_50rJ!&~b3tt|X|v7@5VP`t zj!ANZ6NotxxFHiP+hxsUQnL3&du(_M$6N}A1UM?!nR6Tm_vV|5RjAy8^X2geF3+Fy z(vUY+xE8)_mNv{gE!nZnlG@wSKvy^DS@-?5a?p2+7Vfsr`tT>lZSGsYP0bG5Z?nxC z&z!o?(;c1kcuwxP|Ee?s|Mi8pbgVKpa8bwd27pm1kO}5?Sa~N}&C#DDa4`cx03K|e z@p=vvXk!qtr`l3edcPzDaOSa{6Yj0dp9Wye#)Ms!6ded4X5+1{JL2`do8LP(SdaT7 z4}xI`kQMsvg8T)d!2~YX`VG&Xc3wUKkxqPO4f$|=WBbAz^KF+bNmJXV)?U9_d(TeQ zLC=QiZ9#u1TBv;L?fj@^leo?4JJfQ%efDCT?YH0NvlVCTy=OchJD>F8z<;|;&0pkQ zTW};(7;dUOdC-fPshJ^@OwXjJ7?s3jEs@x@l8tQGMF>K&?(8DLx~!~Sgq4tsYZ4J6 z82rFfDqny9 z?VPSAbEQsm;&-_@?C-r*qa2BMYlA}AX0 z^(JCDoFj$rWVPf$kllI&E>gTf1cS7N;zgp`GXzTK>3AXV{_%G^7nh zZw(y;Jmp9A zVp<)hd%5NT)>{PB1TNRpaLjqgZD4v4Q+BfBVSR6N)#*7U-H!Wjk6vBdprv68o3)tA zsNETXq}(F;=Zwc+MCu8z)qT#R!quFW*p0lTJlG<+mF%>S7Q3+0d5WBDwb_+9up{mF ztWgZR2d=5zz^*LJbPj&(W5-$Hv$dqVp{7y`3rCA0%xcvf|*u<~QDCPlNF!&4`lNfr(hv$;V5ZQt5l z)pT&ah^OlN0LhcEKiBWFM;{0jd(>%bX zTx_*$4{WI;F_wXR_NQ*B)H9%=xz%bNlJp{x@5Mf#ootRHt2yuG;|?~v7sxO&ARDo^ zwcJgWZYaz;M;sM|oS$&0S5xvazscQdH!L9+i<8eKQFSzzD3Nv_sINv$Ci|-qp9_<~UnVOoK=^fn_1C|4j=vc0@ z(1;el34-n*`th14I68f{n#*)4HldOW)0?waSo3eUU3-FjSlekU@Pv?PK6= z$2eA8oZ610b^Jz>_Ukv`$I`Y}t?9UoT#Fzp$bIUbjpdtGm)g#b(_en@i33>AD!s1b z@jnVm3IHXrC3MXU8is$dnC?+<(!qRpRG?@cKqS*@G=Y@N*~*QZw(;W%L2kixG~Q`t zR{VW;By=Y#-nPi@g(Z zPaO2$_I?Ad`1I}pV0Bpmfw(R((C=R?ijJoHhkxCz+i;TR0Yt^?wQ?u@9^~Q5D;U}9 z-yt%maW!N+dZ>K$@s6Zgo$Z{EICkj=NWVnNYHqC3dz4dT~Vd>=ext zyr46jlBz}GDm50ZuP#3kjQqq3nVVk3bvl$3?6o`X(YbZ2@Y4)le#r4zXm9mmW;(Oo zAG*Qyp;%kLriXR~NOjZ6j*P%hF0aqbFyg8^sDA88x7KH1TCWiG_T)yVsCq1*r8`!w zCU9*oi9#ZrN)-%!ZDZyA3a=QE3272GCiT-nC70$A+85m=3lN(~jq! z5aF+zU&%_RdQeDS^G(ygvUJw6LhKk{pSL=TbKP=A-nlBDh-tZ=B90V{{#aH5o3PLh zQea4WT#c9lzwu^aK#Sc4CEY06s+jN_RLwTeh(sWnhIbeM#Rz0XU#oe8Qzc_$9@ROv z5@T~lD7gnpB*TY~g)IG7?1Bg{2*(bpy(xAf#wUqx&135jZI%mw(_tsh{-~oVOA%7g z{_KGuGqK5v=MsHcRYp#T8CePoMGZPF0QT0!FZ^)uB11g$-QvLSk&GbVibPL(@~HG^ z*DJt;GpB#Fq+1y?ego^+4w+{}aDj0wZ(>2*??)W*%eQ(u&eJ@BC~e>0F?yA-4ajB- zq^W8$IPU!H*0!^sv8)E=+lGkxRkM_}$Y1B-ykm(AXEcek_&~f6PderwiPj%&zA}+* zCV&p{FqL2-dgE+TS4y6<;*xi7*`UarIw~Ba{({h+N>p)ay>L^)=T576B(Q21NOQE( zk*?W^ZX(%9o?|G{F`%y!)9F4k!z^_abybWU!e)e+XB0=?{ap9kY^BI1*M#)D32fo74n%*?`rolfdy@F@ytJiszl^V77yzM_$Dt@VXl` zk8nQk@OJUu{^BxUN|+u}l5`sRVvE(?w(1)AAQzrH@m`*mJ3I?bextAz+B@9z@6T&% zDNW5H?95;v6*|-9d@M^CE4;tz*qWmbp7bOX!LsbtvU69~9lOo0rCA8*gC&0`cM`|c z7)H$P;2}0-xuP(eyd6k7sx)p)6+!p+AaO~{b0GW-j%S(wJbE;OHLt}{oR89VekRQ; zoFbXACLufoL2N$#grX)F*^KZa&U#gr7fIjlq}d(v$%~0y_2Vb+eB{(y@2KmzZ~N8h zr>=N>mUYDh_?3ty}NUk^uB0xL1OO3Y`z(MQmgKQO3f>j zO1%CmoueaUD`qQ#m#QSEvRBSUzBV>`LIGjod!I$|0q+NNdq{{#YK*;Z)3<0jVX5 z?x!jk(?Iju%&GJRZwv+pf*KfOiT9DrI|S1JWrCJTXV(*#Bp_~P2tr{k1GH{M^oD>KCMd5uNcpP zK|-LA8wTva9LCf$9^-oyhlaz0|7D|b&`f}aM3?;UP|2sJ2bCw z6Yuf@iQ}$g;(X~LM0xlltDPjj{#2)YtP ztz0k6@APW%{5$DUR^ARY=cfCam)_zWHL7`q{dA54#GA?3)2Rn7L8hb0K}=7MGlSh( zvN{Za6p#9bKW&@Xmv8n{jj zIayBou35aY^owD$*knO&;J1XqS0gn4>U^WP7_S-yLFYtY<$KAYL>-z}5TxEH-g`PZ zi_}3&k?Bm;_lgMIH#cn1J%J|2$!S&TfiSOyYdyJiALK!e029e> zGClYAbs@8zpuPt>WqEijlHZq;y)Jrv4I^<2Q38u)aj?*gld9lAH)SzoDJUC>K#+aICn;^yCU$0sVn{RM;WhcWvZ zh~BK*aK7dl?&W=+>6iza@Weq&4@Erg&&&DVyQKbj{a+(`6;`2>h6^wba4!S!b_ zN|nM<+!v3b)R~Od=Gbgs@66P|B*wn5<1*Oy3=B<;_s;I3Rt7YnCfpELGu=%Fzlz0= z1u?K4>*8GD{NtSG|y4}@?;<2&5-LH)djnHH-6Ko_Kh12lYrS;2S&ZhUq%IA9V z4AD**c{w|7Lq=vCLy z_;5eTEw;_gau;0B%yZ^4fZ5J2S|+uwDelb~KSWo zR|}C=MK}J2AKB;?RqLs&^$y^?3yLn^-Gp6(OJ&F$V%3U zI=vO!pKq&cIs2-HGd&=BL#O;dimhTbb#1p$!a61W+m(s7L0J!@%4 zQ)xm?=GV2So9zM1hm`4s_;(c_kMtU9PWoWq2qcly?PEF?eb1vHU~m=4o~eV$OC(3b^$ASG@Aw6a21y$))LqI zCT)rg7W9~5rSQ^-j?uEg+ys-7=@xjzoH^oi$CTf&4R=KsXE;Y8w+6TU_}TjlIN=18 zxkGME5dBgh`b&B$j@3NGO_D+G2PDU@Em_NbE@SA=Uv<~&6C_WHm6ysSZU)yJ1V~*! zNOmX8q?ih=qz#e1e|JweiFjVvGu~^M?6yHf$HjJEq0IHJ0h6dZASB(*kds8VJTPHe(FlRblDYK+14S2%=#bXK2fiJqbFATD=%HlsV}mITETjg^tvt(O-}^2AKIHEDe8dol@ka8+N;XoBQ7YSI0*VK`mfk?Zf@^QGCIxD7Te#<*Y@j7mkd2Q`#E2Mpj$J3ai+u~ zN6#8%a`PcQad%h|@2$V(uC>vF84uq52XCKgCAeG2Ib%@=*2K;Mx}p7qCk#0&ER5-o z)GrmFE){fR%ovJiyn+T@Z5s4sKrb~|&G+pdIm~n?doYSEi#&A*OGL+e>c_bmqhq2@ zl-^FRXX_n+UR|9=uevAcmiu)M5F7XWf>9<9S&|#mB{S!nn?cHO+1ft%{^0xX;U6%< z3N9RM2r>|k>O9-TBBd}}O6)Xuy*<%{oEMP;uC97rc*jW}9b~r8d9vJv>;K=P^pF_+ z-;zYo&*{s1OUtc;o!=eD$(`}>24@oyrBOruZi`1dGW4AJMTpRnC@8HzXVX+mDd%fg^6O|T#@ z#-EsbLhr1ZX)bJE%MQ_T(FWU0_)p}hQm2PBEom@|jXxTaB$Mpsw*LuLuM06WE)x*E z$RY0Qjs;~wj&7DA&RWi&JuhF-FLMB0(9br?7L9H?ZMH7U*~J%u&x|SM}W$)V8~|8kFIQE4OLIxi}^0ZyDvHn$_+#B(viB zTg*+R0$;5I6m$ez$l9UrXH(_AX6FY@%BUwq9No+vdvS0fYHcFNCyNfZ5Rz&~Ke!W1 z|1nBF%jm3#P(xQsgHbxfhZlzt%>QAOk&1qnOvXR8s86QbH<@JY{>1*KBh0V=O<&4rDBlD1~Ko3_QL7P zZd5AT;u}YY0`R8|&8Nqzo6Pzg<*&tb7k$?*4p>HQI(u_?L~{j2xi1y^pt_2QGF936 zUah+0AI+qof8zjGS>;cROE;^XrtXu8`>V~31y2Qo$UqeH5}L~Y&8pG)BoL`ChH_4( zq@r8pYjl6WWP`hiC4PdA#L=&-1U(uG(dSZ8XQD^-hW0Tp=Xl2|ZRdqqirevW@O^Cs zf?k8Ksg#3qZ<&>(pr3OB2IKTONDmn$bNV4wurJE7aqFtl=Y*;`puZd9rz^B#-01)(SQ_UWCG(yoxyO)vAXlevM8Ql7 zxdpnVS)LOPa9>UCM_Lr zNUVro%?&2d=8T8&Dj7ci$GwU1ZrQ&@JPbcCSf8Ut|65I?#LJ;j=l(pZco3Vv$U52k zy%8P|y?t%JlzKltWU>R$6MxBhM?nVd8YouqmzN7lIgE+hk!mC%=vO;VvLF3Zqin8G z&F`rOw)=xBz&Ab08f2r}X51PpO*Sx2ddXKPZt;k;flJh3Ee@~INCiPfgFNF>+R*`rt zxxGaYxgF&bJ2xTjco7R42-}x6{RJeQ2ZTlAb67h=@&8T@R+2BB8l=x%5cRRC z@v9Z%3kP_@Qe0$|j|%D^mmDYcqG|qb*Er-s)x3Y=2|JsTK|W;;&D%XOo}K9f;V^o? z1X`48HSP?&*|{aXuNAs5<^}l$v)*Om_0etFR%4+H_Nj`|*-d}$v|4}aBd;LZj(syY zfwE=l+-2#U{ka3&rzhNu@;NV^Nk2xmwK&en8zt${0*IYawB?AgR(}2SyVU1G=Fom0 z@TJH(y4iX^cqc;+ic@{aYB679f_@7)5zK=erC)T44sYRK6?fV6=6z#97givFc3m=9 z_d4ao{kX#(0|XSOWU$=1Ei38}v~A$-0>dh|V?IQyK#vYwhf$p& zWCQFpT)R=n>m3lbRWz|w-LGmem#)@7Oi@}oA3MmEmf}%k0i0n;o-`ufm2E9+F6bxg zsl!g2AaZTtZlMycCS&@@BL*INU@wbZ6!hsg?n~Lr5?dIOSYEDe`}u=soclYlEK>RW zoeZ_K>2Y@cfQq$rMDBr0!?nv`=&r^$cc$=mB`<8hqsv{L6VaQqfU4~BSgX7LLB$q^a zduIbY^a_akghGV8yspa717}}15ExkQOM;Ja`6*~WNFg*b^v{opZn`tCSulRcMRlNr zcC)7|Ro7h?Lmr~$5AQx4F^i%xuAYeg`3frH+-W@=X!Vh(v^0~vWodlvAg5ajVeWLm39?RtnaQK>_UBtu|$WM;2+M!zvgnTu78eAxG-~JJz?T1NE!=c6X{@8{Wun42$w1gRFp1>iMfX|vphyZqhqB?mT|K! zkR&DO9%=L9Q>_M`7OMcMALo(u3AD0+PREGH;J=F78-LVJ{1&t-y64M}#i5Tri0nnY zN^+-O1?QyN(VsIG!xibV@gG^pS$~>qOEm0b6x(hJJ6XWiEeIFm)K#<@HTv>sQ!lEG z!{vrY!!dgm#FnqZ|s*H~8Bx>8u zC#&3h4yiSW3N-&yL4If{9(Ry)7d9GFala*bC9yjFw!h5EI{N7Uuy-Y|?o&}XiX!Nu zK@nYa<4w|Jp<Q7Yn!w&_`TQk_4n(0o$}pt?is#A6vy@8;MhcPlu?^079B+#e>Dx7*a3L=uA9}eb6z|M?%^tX{( z`y-&;IU){b1*$ilCF=x9*U-SZ-Hk~_sGU@uS?eFVKBL7kR~-a=6y?5_5l+%WTtBb* z{CTOE5KcT?I#WXpY{S{T{kk>bu^?kB5aKze+we}i}NUjMDG_w zv)ut&(yZx~{s}MW3U?f|(9`m~mJ#;SL!7Hg?4$B)#Vmt8q}*^|!6VHl4w|CUKv(cv zjW#loR@xS(qVC1S3tUyqatw;crmvb_IuqOfa6+Xw2_(ZSX44xay$O zYkLsx6D@!!FGSn#Fd;nH`UcUk19k>P;+j>&fZ#PI;h4q3xN~%c$Fz)~5^Q_V$-|yq zDk#e38fQX0MnpDa_)02bPZ$m!9WqGFJ8)|lkUpJ=Pp=&9cPkaia^>NWRZRnf*uq!A z(#JtXw%wpNYBXA{X)viu{~q25{H>XeEd3C=8J*=bAKxd>*)VoUb()ph1;N4dWCQt5 ztS}C^TUXdu%L?Z#PES5hC%i|`ws5XHUQk_38O4``nNs4IVMyuMF2kZ)l(LY2nJQ-KTgrtCI#!br%Z|Nb9(IhU>32zk3aSz+i>3ZUblS^JO z)EK2QX`ltAXh7|I-{;-}Ccb13yeNk{T2><~4LIODnnydmQE(Glb);J6nh@?+O-`eU zxEsHJK{c-j>R7)o7t1ja!D_n@>I9*$JNa+cFH)k`BrUds+2ELPcGuM>VPnCpIs zcsVt46dmCvg5k*DIAY9LYSFkkraL`^nX)f=w_gdI%1qwTTPf<^U1n&9X?E0AMY+oc zpxI%Ud^y9s+D15xjRq9q73YN2MHXf&v1d39nN1=j=w1!%Ty@9obyXl z%&Vr*&2K;c0^?`?Xc^T5(;}j;LQl$luObh$4Wm948q+5ED=SsTjo=y2!IYzxH-s(6 zuA6eZv=;FDo~o~r^?VSEvPc1PN1z%Rew->}@=u7AyB-95r7JwCWr$-J;{YG(9M6@C z1ZvTtN1=^*0<=6ISL!p+>qw2I``TQl*7^0j)3uM15qnpUP-9s!pJC9 z;CWr)UM)jBt_z$#r}%zbp}JJdBK6TGe;sM`AAd7QvXjiaW&M_lpQ^G|af?n&hcBEp zK!Yy&IT4Y2e}(%G5saLN*-PyWQK1WGgRuK&Qdml0FyzKG)LY%%4jn3xtOk~^IIFO% zLT?`*1l*sk*t5c(@%$R_2?<7)3Iy`p`NvTE(GiUmKYn9Cp@4Ck^NTMVt~LX@D_l2L z8RvumXh4_0+K3oSw2e)=A7-PRl|(6P2vRH(ePpK=b+=vo5b&q8HqkqgNyvP6G%*-; zos%W!7i*VPuY2&=(doI0^M0Zlgmw^cov!eNmL<+um=S@v`_rXDp-fc&0v`L122*to z6wR>)vR^Zm;8S2CvX8nD#rT1+N1Hv4D-z$GmY0|f>{O6DMADGbQA-(XD;;>Od<%Qx zPKpLyZ4zdqEuhX<5!)I-#WNfSmHH~AXwo7XnK#uFvUu+d|6 znp8wK^b$Ofs)M963IwYE6ex2+yhTH6wy-5m7j^(5hr-QfWPCh*JL21sO-5KL5pbCN z9(i+XZAho@8jK;cHVjQjX=R+MBp?m~p41hd(XzxHI&h^n=erlHd5t~`->Lla8`VlG zrlUxDlAcR#(5wK*kgK`{+#J{o7b12NT+p@1ExdV-)hANS3dTTVty3`;!K&!tq+BxL9es zM`y5ITPhk1EW-!Tmjr!aY@G6Bw4Y5`xUHB2X(zD`Y{ZowLW#tadcvOyP=kxn5kcaz zV#|3CLPv4|wT$8kjlSrX$CvbVr0%2qnXW20nockBjx~lD%s_PzyM|#&b-20w`RaYD z*B%5Mr7LLJ;+(}txkC66rNV$dj{)V-4J2PBELxhaR@$~}igUK&L`O4;nvac9fjx)4 z>m+Q_A+c8BNLmX3bcE7Id1=($gP?<(rlSM6w&z1&3e=WMOk_b6{I0+WGP@(!o^3Ox z_3sdOV{G?Zbc6x}SqsOnSH|PI#HZl-w|ds9?g|gh0)B2 zOYVCqd8sO3G^_}>R2g)1YxS$IZ1zZQgGhj!H1r#np$eOb%|hj8{8C~gdQa%3FZ%9g zHFdw8;vlZjmKgZM3Z)!TLL(z`v)wwbP1Bk5JoScF{R0?6?8Nom6P?bYaY@z5tc>#c zy2RHY`n7uIs_qIG2~8P|pvI~`Q6P&h+EWo_x5$hunv>W+=zt(uTE@#G`osr0piR8K z>DIUA&|l*qD%i_cn&wPQ@!%=K^oh^3>p%n^*?~BuXk%JH=FwK-QEp=z^y_BGZGZTM zKlb0BbnArXBp7#Up|&f~m@tES)I?6y%Ec!Av(j34DXu%Q|INC>DO!e5Q_&yPqBu&w zzd%GM@2bi`aWT|3#Fq@C_}Z<|m)#JG8!eIJih86lo83(}=@X>x9Bc)=^$*_R-|6~0?{QW(}G$!Y+T7(>Ze5f-m9bAoYD3iKOC@gqFYv{1gI*Rf8DOT_T^yxF-D5$3&YWG)w0HoODgJb(uzcM zzLxe6VBh?Wh$rKh=Ga@TQ?=JCVYAy(M>+yB$Mx!l?cHBM%aW>U?c^cp0%kM$`NPLg z!ZwMncJ47j%9{Vn0SwVMYxn6i<+D?EwkD0ozeZV2NdI=BUgi-kYuqU{W@0E-)V=fz z?d}ply{lqkSAASl`^dE;QutM}>2t0-RHoYAhMcGcCRCIM3IFd$7M8)^5VmjT#iFCp zz>RqBwQ-#jO2pmr5M~cSv0PU7;J2?aoRDJlhs8x;FVz+9*D?n^+|^nfMY8Dh!61;6 zTVBxMaJrT`9^emiQh|xiKa(oNNRohA zyPWJeF!p26ph-U)nzdJ2rWlZ}h(|X*OQ|E(OI!ykk*$Ka{59B!;3uKJ6OvVs!;3I?sW4|Fl$xa-NLg z?6Qo>XH{h;*9adI+%}o;nEZ3Qkw%S~`?vQPMn$3Q#8_gG=(;x#+&b{WMs2LQqHo3E zR~F^ccArj@U~DL*M#1+rT+6#-m2o%e5nQWfkIT3`WPyoJW0J{IC%f)Y2B%016Dbs& zZ%u!&o@rB3kc0jKh7+-{!G>igs{{${adf9pOQuyK(Yx|aTSRT#HBgic%^1T#^ATe1 z`7!7Rd?Cez!S?dM@1iW<7^`)ELXSY(0v_QKlm#Ywxl|sYD(>Hc3wHnM%~g&n60Fjs z;?{f$%3+vtbNn-rHB*6il8|qp5XLc4dOJzxEP7w)*{N>_a}7cCG@;z0SJc~vLIt=# z)oGj4J4gBQIWSlWs5);r*hCFwx|6o(=WMam9oXY={mKFpoukIqAv#*!etdsam6Vo| z;jrU$CCb`fr{@Q~k+&rq=^p5q6^KcpW3}bZ{(TOg$(9lnn$PARRiO5qGHkdxOY;8J z(fhxlfZZ~3WV+{x4y6e{biX&jtQ77ovgk+a3NLHhz?u4G#U{FEtQEZ_m!4XtnnCCH zsCiPPNvxfV4*}m+6dR8-M6-%%qBO6O@X%B(TXDc`@Ej0*t9^NoRADcazImhfn?jeD zvHT>_mJdrcl&bS4bz86LAzmV)U#TmAfSKb7{o(=>o$srDSVR6BTXu5U5TrBe?~kG} zYS)Nv1ZjMUlu^6Ws_I0W`0RZ?fl5!$}`AM;)*Sy)O)d!t3-! zOv=DQW3TFambx|h=;i6uCi;0hHnLj7MbX@l;ZRHmte$k1bvZtqd?K5H-HQ8 zx>Vtz+NG28>G8E8bB##k^&rHA0$falhBaMYb~^0~r8XQ)ZvXqiXN`=U4?C2K@j5{> z46ZYpjmR$){>)LW>*5B^7;yc^G6aP;%Km&mn|8Ym@Z(@>5YBF10N8LHf{WW%h zj6y_6zTuW(ePgk(QId6oj<+TyvW&WmAR+h-y(U!QHj|lWFN-F?^T2nCG52a~JfRLk zcf1JZc@k zZ;(gDB%Ljbkuj$?QB*8iEJwX7^XSSkAmQ~U=b@}_f0PsbMLjmYVLLdzz(l8UIin0J ztb`f*rSErYi}ZDuki}L>I+gb3$F#TYK4&+X@-Ua~i13=LVg`vaFsnQ6D%{{kmymdo zLABLu*AMuw!C+#2=b(jXlIDph=b^vBiH@Ho&eFDo0u!C1>}4VcZqNlm*?QlfViI6T zDL@bhU!a2=AU4PTv|)b+RXGQ}b54cak9N#mPr_>duU0Fyc)56KYW;Hm7<~4Am)@K_ z)vB)sWLgl2MMvZNsYZq0(*=&>QTNmI2p-f{#s5VAJCrGxlQJxECVXjO-%j7$nICs* zt_;)7!Hw=+SD#+;z)jbAF*fea%_fCQ}3#$!hfxI}Ff44FmKPN@hc2NG}p-lF4N zVfcvsQaMec&)oH|_rF$;;%04S{7>`*U~lzB1}B`w!brRY&3IUMMz@+@_@6aA*H(m< zrroxcJroyPbz-N52GzvLna!qUe8bq6bx$}O|CsDvO+D+CnKgG+UZK_*e^E!vFW6!+ zmg~~iBp95Qodq)v|7_{aW zL9a8YiZt82V=q{b4w!U#{z?rMGo~Xp=!PXU?5FZ!*Z100+E5~t#`0SJ7j?wY5w|2W zPHzLxk>{x?Jrj%Z5}utQj33 zEsZ^@nlNJh)*QBj-l)~6RbA=Or;P^Xsbxx#yo)anu#n#J!OdiNy~av3cPT($1;v>m zjW)PJOB>Akm)}rB`G1t>v^b+o29feKF;jBC9kIA|n%Dj(dNR+;{A-lO8Nq~9g3QWL zW|)9eAJ&YC%<{bzID;30LB7=qds-M~tdi|MET4$={7Rz>_QRa~xzxbRLrikG$B z;gC=OuP7ps!n4kANi|vhdb2xezaNZypLMv+uT|~E?ej7>;FWmhtc^uwfnX3+W$gyC z_j7;L8+{R$tXSWSWHcR_^6&!KD1P}3#yiIqE^ETDzGPSy8NGY`Qpy04e$e&9b>LY& zip#X!0VFR3%eg`4FC-xAn`@#Uz&HfuWjgnpZhJgxsA^k;#qGt=815itEi4$;WeW+4 zPrv-47t_uejDjY)`gnZWhcs)k`Z$!ua*j`fE;$LpR{s~HZ2thmkUfk1J1AuXqB(Gs z9>odTt^n7)820=S67iv)ba-SrF5rMEfvE)L;R%ZA;f|EB;g|`^zq1+L z=t$=$d7R%6U5SizS>&P2dgE8ZN>zhle?Z4s5YQ0IiO_UI27qp$LFG$>iEVrl_vjWX zm;;1vvpDN2Q_u^GhBR^ocy%Q6CuE#&k1 zQtYGs3;a*?fA-yf$kSl`|B(wICP`t5VfJ~Z@-w|Ha<@yv!7Z zaR5IXD~spWcl&J4_vzVTqeLkZ$-=@?%8IhEVj)X*?<{7NG$e%-*(fEGg^grmV=sRT z?%eTWE=AmP=G^nCx$ko7t!M7}@%&iE`Xf};cj&KS^XVYzVNv!pJ#K|RQ3L(=x^lcN zRMiiB@$_5Yg8wLf{p@Xe91Tfrey&Wf+jN8FJEpf921$nzM!zA0+1>Pb5RxjTPW5Nc zU{+veSM`VJB+cEQ$#70KJuZf%K1MAv&}C-KI2E=U3LSLJCo&WjxzO~u9@6?)=;B=- zqP!~0>JO3l!e0*8VMRsz%h#@i#6C)y^j_VgkDeI}_DZxHp*W0dO(&DdYh_2e%FDWR zA3JyBd@3^78@)Z$Khg9!5|S%$i>~eCIjV6p_&tfM2pmBBCC8c`H$!p-)T966r}YM^ zQ6paIp#99-Z#&-fxEhixFw9qG%T7sdAW%=|if8QlWYI~0^Xan5*s#ttJ|$u}wkiXx};t5R*fmBP)O?sQlU^}?CMXhuAmbQ6f;z>%&Z^1fMjDx~)@mi zxob4@nkY8{%OR69iLr~i;cGeK+oqMxh{cspMaOx27PIvfL_Liwo;g6?nQGKFyocYU zM-kO#-NBIFP8oq$Sb(G(>MdJ=*o~YZ7MMvg-K~AI?oh~pjKSoNn-c~yM9Eo7kRrTa>h7jU{4Irn??2pDP3$C6Svns{Hc=2FN&xd}5r(5>fy z8S3eKak|m8e7cqVu2@B6f3xOT$UyRc&LC7o@6N#*=tUyUpGh7L=Qc}qTub-XV-`91 zd*qyExc5dShaQM3hjW{yI;O+8?&>4TjT-00G5YIgvfa-)DeYUjUk(|NQCQ_k>2zN2 z*9v2Ns48*0WmGyUqWiNfM;}wLM-g4lY;q@IZU!#uo_*6e0h-ax+(?_>&f`E?B}#vw z9|)O{k%;Z{_B+s=U7fnuH~fZi2jMh2K;tZjt@%n6-IZmPY6*d^e|RJp<4by%Oy%SP z)kdK^;5696{K-U?GN{?m_OY)9dTMd?C%%G_ps|*(ZXO36<_?G57g1Hwj9MQyYYvB8 z$Vfz8b&i}oH8awQnku((j5K#Hyj``&j8^WS*}HoeC!!z#;49eAz-wRgW|&SzPy`Va z6%oHJ{OZJSE&MA6Mbx(lI-2+C;(Upf_X**+z zUN%H@0xvOf#w-!Xq`N68Ibxn|2V47ZOZiyWSK+YRi4{;$9suSEDJ57%f z&5)UWXyVYkpVt@cSc7P^99;nzI)|LD;2$nH=Gc+KE+!&JB%;?QY7ZG(6L${~^l=h9 zjFzLTQ#YZabiVbX5j#v7CXOkayVJr9_~60VPP>PokE{51w-Ot!JVQKR*&$thje%}g zwyq-Lg&C6{(%?be$A8$G*l-6sHbF&seYGFQQ#Pw~D^6&(7NE;i&a2O|Eq9<=gF{Xy zCR{>w94gC0M`x)XnZ&6$_xK?Ic>S?9bI%s&@7CbITuv-lu?p3OjlQ~Da%$$I5a*k` zv9(-*^tugVVl5_4nSZs~mI8a2hYRa4XSE#tEauJ`D-I~Y>jsl!CW&Wl1!88=y*D9E zZ+QC}>^p%Oqn9Rk9KhU$@7~)xpqwV}VezMs5XIBB0_91H>@?nAeSlzYvfjrB@aNEa zpg+K#`^JtZi!t9|Vj_|l=>;Jc`KHaHoaoZrVjkRb9FM_$I68~@m zM>&)j@EMocAF3YdAv<{pVX@(&f-pJyIN!UV)IhWLaSq@p`w|0o;4%lyRP`3a_tt{} z!ppZVDg?QDEr8EQbNg%ba05qLn;38rmpf|t{hYd)1=bGmPi=#bCinkH_Y&KT`ShV)s4-#!{LaAn5$F8vxLyojq#SyV%aolLt zvewPMJOhpgIE$m5O^UY-`?cfTeNcG$k)1F(VwXCX#+zLxg`F;oNK4JB(V8hwc=y?d zORV;t*n|D{mSJr)MR~nTT_hF#JjUbo$&qee64TGeAM!%;ke9BMC`EG>cG>Mau?73x zHrS=7P(J^)*yOy(A~unn6yj_#^t1jUPmIYQp){jS(EiS2>`aQc9s4~k%R4+JzWeaL zR%b^d65B&h^bWP=+)P;7WY@fL8yB6A7a;fm{uGX}H!0pa?6(G@9>4Sf44S*~;+1n0 z1AS7MzRb4+^mre9iAM8oq0KuS<3Un99P~VHx{va3uEC_c#eW`J=qC9cZr|bw`=I=k zf`ziJ4c59e)>8~>)L&RGNgJ=5v$qw;xSJI2CJwp*(dd6|t=@2Ylc#8p@oYz4I2OAN zA!4WRO}6WnpP1`*`}?mo&`ElkFBAw~r|w5_j3-Iq&fuW+5RFx9{SYFiY`re2)5wn_ z`hagj%&F$SX|(HHNVwU0;i0Sa9S}TR=_P*`xoB$@fi7 zW2V)(C3(*8wP}`u@6~?EWR`ChuysUAzUaRf0D$FC0Y>=A8nd*B7|atholL16wwRoq z@u1?ZJZjp!R`R?P*Leo>7DV|>Hv`!SKVxz^jhA)sf&M*BGph)|GQ3!7zN*{vB;DT1 zD)oR{Flpy5zR>!hc;|V>Ip!@&_L&+XT_3Œ`g=nh63pw9&cVdn#VbV}KUEd8jo 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

(|EKdECeR9bkl+}&4+ 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 literal 7987 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 From 7387ef60bec9d9d68e36127896075391431bb343 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Thu, 6 Jul 2017 08:53:48 +0200 Subject: [PATCH 040/912] docs(readme): add badge for projects who use gin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 191546f..fcf3b8c 100644 --- a/README.md +++ b/README.md @@ -998,7 +998,7 @@ func main() { } ``` -## Users +## Users [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. From 34e972e15545847386a54f9268141cd3a789170a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 6 Jul 2017 20:59:27 +0800 Subject: [PATCH 041/912] Separate type define (#983) --- render/html.go | 46 ++++++++++++++++++++++------------------------ render/json.go | 14 ++++++-------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/render/html.go b/render/html.go index cf91219..332d3ba 100644 --- a/render/html.go +++ b/render/html.go @@ -9,34 +9,32 @@ import ( "net/http" ) -type ( - Delims struct { - Left string - Right string - } +type Delims struct { + Left string + Right string +} - HTMLRender interface { - Instance(string, interface{}) Render - } +type HTMLRender interface { + Instance(string, interface{}) Render +} - HTMLProduction struct { - Template *template.Template - Delims Delims - } +type HTMLProduction struct { + Template *template.Template + Delims Delims +} - HTMLDebug struct { - Files []string - Glob string - Delims Delims - FuncMap template.FuncMap - } +type HTMLDebug struct { + Files []string + Glob string + Delims Delims + FuncMap template.FuncMap +} - HTML struct { - Template *template.Template - Name string - Data interface{} - } -) +type HTML struct { + Template *template.Template + Name string + Data interface{} +} var htmlContentType = []string{"text/html; charset=utf-8"} diff --git a/render/json.go b/render/json.go index 3ee8b13..4e3b66b 100644 --- a/render/json.go +++ b/render/json.go @@ -9,15 +9,13 @@ import ( "net/http" ) -type ( - JSON struct { - Data interface{} - } +type JSON struct { + Data interface{} +} - IndentedJSON struct { - Data interface{} - } -) +type IndentedJSON struct { + Data interface{} +} var jsonContentType = []string{"application/json; charset=utf-8"} From e0fd6238d3cb237d3eb4c3d13792caa265ecdf29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 6 Jul 2017 22:49:54 +0800 Subject: [PATCH 042/912] use comma ok and use single line (#984) --- context.go | 10 +--------- gin.go | 3 +-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/context.go b/context.go index 2134b8f..604dc4e 100644 --- a/context.go +++ b/context.go @@ -562,15 +562,7 @@ func (c *Context) GetRawData() ([]byte, error) { return ioutil.ReadAll(c.Request.Body) } -func (c *Context) SetCookie( - name string, - value string, - maxAge int, - path string, - domain string, - secure bool, - httpOnly bool, -) { +func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { if path == "" { path = "/" } diff --git a/gin.go b/gin.go index ca8d75e..3cac936 100644 --- a/gin.go +++ b/gin.go @@ -26,8 +26,7 @@ type HandlersChain []HandlerFunc // Last returns the last handler in the chain. ie. the last handler is the main own. func (c HandlersChain) Last() HandlerFunc { - length := len(c) - if length > 0 { + if length := len(c); length > 0 { return c[length-1] } return nil From 7eb943e70aa5d526ad61d6a6ab576ef01bcf29c8 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 7 Jul 2017 08:43:47 +0800 Subject: [PATCH 043/912] Use single line (#985) --- utils.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/utils.go b/utils.go index 18064fb..968570c 100644 --- a/utils.go +++ b/utils.go @@ -100,12 +100,10 @@ func parseAccept(acceptHeader string) []string { parts := strings.Split(acceptHeader, ",") out := make([]string, 0, len(parts)) for _, part := range parts { - index := strings.IndexByte(part, ';') - if index >= 0 { + if index := strings.IndexByte(part, ';'); index >= 0 { part = part[0:index] } - part = strings.TrimSpace(part) - if len(part) > 0 { + if part = strings.TrimSpace(part); len(part) > 0 { out = append(out, part) } } From 0c3726b2061604995defd15bbd673dc4d7065e1c Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Fri, 7 Jul 2017 09:21:44 +0200 Subject: [PATCH 044/912] docs(readme): remove logo, relink it to gin-gonic/logo --- README.md | 2 +- logo.png | Bin 87064 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 logo.png diff --git a/README.md b/README.md index fcf3b8c..4117769 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Gin Web Framework - + [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) diff --git a/logo.png b/logo.png deleted file mode 100644 index 7942ad563f911cf8e828e140c549e1f7f7c34b8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87064 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Œ`g=nh63pw9&cVdn#VbV}KUEd8jo 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

(|EKdECeR9bkl+}&4+ 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 From 75ed286c6038b2a366ecf033a79d31764eeeb041 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sat, 8 Jul 2017 01:21:30 +0800 Subject: [PATCH 045/912] feat: add SecureJSON func to prevent json hijacking --- context.go | 7 +++++++ context_test.go | 26 ++++++++++++++++++++++++++ gin.go | 25 ++++++++++++++++--------- render/json.go | 26 ++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 25 +++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index 604dc4e..acf2cd2 100644 --- a/context.go +++ b/context.go @@ -616,6 +616,13 @@ func (c *Context) IndentedJSON(code int, obj interface{}) { c.Render(code, render.IndentedJSON{Data: obj}) } +// SecureJSON serializes the given struct as Secure JSON into the response body. +// Default prepends "while(1)," to response body if the given struct is array values. +// It also sets the Content-Type as "application/json". +func (c *Context) SecureJSON(code int, obj interface{}) { + c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj}) +} + // JSON serializes the given struct as JSON into the response body. // It also sets the Content-Type as "application/json". func (c *Context) JSON(code int, obj interface{}) { diff --git a/context_test.go b/context_test.go index fe31f09..96e9f08 100644 --- a/context_test.go +++ b/context_test.go @@ -598,6 +598,32 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") } +// Tests that the response is serialized as Secure JSON +// and Content-Type is set to application/json +func TestContextRenderSecureJSON(t *testing.T) { + w := httptest.NewRecorder() + c, router := CreateTestContext(w) + + router.SecureJsonPrefix("&&&START&&&") + c.SecureJSON(201, []string{"foo", "bar"}) + + assert.Equal(t, w.Code, 201) + assert.Equal(t, w.Body.String(), "&&&START&&&[\"foo\",\"bar\"]") + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") +} + +// Tests that no Custom JSON is rendered if code is 204 +func TestContextRenderNoContentSecureJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.SecureJSON(204, []string{"foo", "bar"}) + + assert.Equal(t, 204, w.Code) + assert.Equal(t, "", w.Body.String()) + assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") +} + // Tests that the response executes the templates // and responds with Content-Type set to text/html func TestContextRenderHTML(t *testing.T) { diff --git a/gin.go b/gin.go index 3cac936..8388ac7 100644 --- a/gin.go +++ b/gin.go @@ -44,15 +44,16 @@ type RoutesInfo []RouteInfo // Create an instance of Engine, by using New() or Default() type Engine struct { RouterGroup - delims render.Delims - HTMLRender render.HTMLRender - FuncMap template.FuncMap - allNoRoute HandlersChain - allNoMethod HandlersChain - noRoute HandlersChain - noMethod HandlersChain - pool sync.Pool - trees methodTrees + 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. @@ -121,6 +122,7 @@ func New() *Engine { UnescapePathValues: true, trees: make(methodTrees, 0, 9), delims: render.Delims{"{{", "}}"}, + secureJsonPrefix: "while(1);", } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { @@ -145,6 +147,11 @@ func (engine *Engine) Delims(left, right string) *Engine { return engine } +func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { + engine.secureJsonPrefix = prefix + return engine +} + func (engine *Engine) LoadHTMLGlob(pattern string) { if IsDebugging() { debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern))) diff --git a/render/json.go b/render/json.go index 4e3b66b..8b64f53 100644 --- a/render/json.go +++ b/render/json.go @@ -5,6 +5,7 @@ package render import ( + "bytes" "encoding/json" "net/http" ) @@ -17,6 +18,13 @@ type IndentedJSON struct { Data interface{} } +type SecureJSON struct { + Prefix string + Data interface{} +} + +type SecureJSONPrefix string + var jsonContentType = []string{"application/json; charset=utf-8"} func (r JSON) Render(w http.ResponseWriter) (err error) { @@ -53,3 +61,21 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } + +func (r SecureJSON) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + jsonBytes, err := json.Marshal(r.Data) + if err != nil { + return err + } + // if the jsonBytes is array values + if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) { + w.Write([]byte(r.Prefix)) + } + w.Write(jsonBytes) + return nil +} + +func (r SecureJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonContentType) +} diff --git a/render/render.go b/render/render.go index 4629142..620b0d8 100644 --- a/render/render.go +++ b/render/render.go @@ -14,6 +14,7 @@ type Render interface { var ( _ Render = JSON{} _ Render = IndentedJSON{} + _ Render = SecureJSON{} _ Render = XML{} _ Render = String{} _ Render = Redirect{} diff --git a/render/render_test.go b/render/render_test.go index c48235c..e4df5b6 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -66,6 +66,31 @@ func TestRenderIndentedJSON(t *testing.T) { assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8") } +func TestRenderSecureJSON(t *testing.T) { + w1 := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + + err1 := (SecureJSON{"while(1);", data}).Render(w1) + + assert.NoError(t, err1) + assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) + + w2 := httptest.NewRecorder() + datas := []map[string]interface{}{{ + "foo": "bar", + }, { + "bar": "foo", + }} + + err2 := (SecureJSON{"while(1);", datas}).Render(w2) + assert.NoError(t, err2) + assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type")) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal From b060a5f409b8d3d24bf547cc737d688743fd8810 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sat, 8 Jul 2017 16:16:59 +0800 Subject: [PATCH 046/912] docs(readme): fix code link (#989) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4117769..eda1dd7 100644 --- a/README.md +++ b/README.md @@ -471,7 +471,7 @@ func startPage(c *gin.Context) { var person Person // If `GET`, only `Form` binding engine (`query`) used. // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). - // See more at https://github.com/gin-gonic/gin/blob/develop/binding/binding.go#L45 + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 if c.Bind(&person) == nil { log.Println(person.Name) log.Println(person.Address) From 12508320c2834204677e29be58fec8103facdaf8 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 8 Jul 2017 16:49:09 +0800 Subject: [PATCH 047/912] feat: change json lib to jsoniter A high-performance 100% compatible drop-in replacement of "encoding/json" https://github.com/json-iterator/go Signed-off-by: Bo-Yi Wu --- binding/json.go | 3 ++- errors.go | 3 ++- errors_test.go | 2 +- render/json.go | 3 ++- vendor/vendor.json | 6 ++++++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/binding/json.go b/binding/json.go index 486b973..aa7039d 100644 --- a/binding/json.go +++ b/binding/json.go @@ -5,8 +5,9 @@ package binding import ( - "encoding/json" "net/http" + + json "github.com/json-iterator/go" ) type jsonBinding struct{} diff --git a/errors.go b/errors.go index f38d3ff..19b2cfb 100644 --- a/errors.go +++ b/errors.go @@ -6,9 +6,10 @@ package gin import ( "bytes" - "encoding/json" "fmt" "reflect" + + json "github.com/json-iterator/go" ) type ErrorType uint64 diff --git a/errors_test.go b/errors_test.go index 1aa0cdd..46ae536 100644 --- a/errors_test.go +++ b/errors_test.go @@ -5,10 +5,10 @@ package gin import ( - "encoding/json" "errors" "testing" + json "github.com/json-iterator/go" "github.com/stretchr/testify/assert" ) diff --git a/render/json.go b/render/json.go index 8b64f53..3896740 100644 --- a/render/json.go +++ b/render/json.go @@ -6,8 +6,9 @@ package render import ( "bytes" - "encoding/json" "net/http" + + json "github.com/json-iterator/go" ) type JSON struct { diff --git a/vendor/vendor.json b/vendor/vendor.json index e520540..547bb2e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -33,6 +33,12 @@ "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revisionTime": "2017-06-01T23:02:30Z" }, + { + "checksumSHA1": "gWQ2ncPI6qpTwS3e6/ShPwUP1uo=", + "path": "github.com/json-iterator/go", + "revision": "b1afefe0580e6e818dd50da9593f477c80ccd67d", + "revisionTime": "2017-07-07T13:43:33Z" + }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", "path": "github.com/manucorporat/stats", From 08338eff821be0a8805aab51ac35eac856e6a3ed Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 8 Jul 2017 17:03:14 +0800 Subject: [PATCH 048/912] fix testing Signed-off-by: Bo-Yi Wu --- context_test.go | 6 +++--- errors_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index 96e9f08..6c4af42 100644 --- a/context_test.go +++ b/context_test.go @@ -582,8 +582,8 @@ func TestContextRenderIndentedJSON(t *testing.T) { c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") + assert.Equal(t, "{\n \"foo\":\"bar\",\n \"bar\":\"foo\",\n \"nested\":{\n \"foo\":\"bar\"\n }\n}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 @@ -595,7 +595,7 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { assert.Equal(t, 204, w.Code) assert.Equal(t, "", w.Body.String()) - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // Tests that the response is serialized as Secure JSON diff --git a/errors_test.go b/errors_test.go index 46ae536..85782c0 100644 --- a/errors_test.go +++ b/errors_test.go @@ -91,7 +91,7 @@ Error #03: third H{"error": "third", "status": "400"}, }) jsonBytes, _ := json.Marshal(errs) - assert.Equal(t, string(jsonBytes), "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]") + assert.Equal(t, "[{\"error\":\"first\"},{\"meta\":\"some data\",\"error\":\"second\"},{\"status\":\"400\",\"error\":\"third\"}]", string(jsonBytes)) errs = errorMsgs{ {Err: errors.New("first"), Type: ErrorTypePrivate}, } From cb524fc94e322efd6b1bf82688af5109cffd06c7 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 8 Jul 2017 17:11:28 +0800 Subject: [PATCH 049/912] fix testing Signed-off-by: Bo-Yi Wu --- errors_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors_test.go b/errors_test.go index 85782c0..778ecf0 100644 --- a/errors_test.go +++ b/errors_test.go @@ -32,7 +32,7 @@ func TestError(t *testing.T) { }) jsonBytes, _ := json.Marshal(err) - assert.Equal(t, string(jsonBytes), "{\"error\":\"test error\",\"meta\":\"some data\"}") + assert.Equal(t, "{\"meta\":\"some data\",\"error\":\"test error\"}", string(jsonBytes)) err.SetMeta(H{ "status": "200", From e23842ecab161390b6b537c1c906d7e713f07db0 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 8 Jul 2017 18:19:09 +0800 Subject: [PATCH 050/912] fix json sort the map keys Signed-off-by: Bo-Yi Wu --- binding/json.go | 4 +++- context_test.go | 2 +- errors.go | 4 +++- errors_test.go | 5 ++--- render/json.go | 4 +++- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/binding/json.go b/binding/json.go index aa7039d..20aae8c 100644 --- a/binding/json.go +++ b/binding/json.go @@ -7,9 +7,11 @@ package binding import ( "net/http" - json "github.com/json-iterator/go" + "github.com/json-iterator/go" ) +var json = jsoniter.ConfigCompatibleWithStandardLibrary + type jsonBinding struct{} func (jsonBinding) Name() string { diff --git a/context_test.go b/context_test.go index 6c4af42..f57fa6f 100644 --- a/context_test.go +++ b/context_test.go @@ -582,7 +582,7 @@ func TestContextRenderIndentedJSON(t *testing.T) { c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) assert.Equal(t, w.Code, 201) - assert.Equal(t, "{\n \"foo\":\"bar\",\n \"bar\":\"foo\",\n \"nested\":{\n \"foo\":\"bar\"\n }\n}", w.Body.String()) + assert.Equal(t, "{\n \"bar\":\"foo\",\n \"foo\":\"bar\",\n \"nested\":{\n \"foo\":\"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } diff --git a/errors.go b/errors.go index 19b2cfb..26d4e47 100644 --- a/errors.go +++ b/errors.go @@ -9,9 +9,11 @@ import ( "fmt" "reflect" - json "github.com/json-iterator/go" + "github.com/json-iterator/go" ) +var json = jsoniter.ConfigCompatibleWithStandardLibrary + type ErrorType uint64 const ( diff --git a/errors_test.go b/errors_test.go index 778ecf0..a0d9550 100644 --- a/errors_test.go +++ b/errors_test.go @@ -8,7 +8,6 @@ import ( "errors" "testing" - json "github.com/json-iterator/go" "github.com/stretchr/testify/assert" ) @@ -32,7 +31,7 @@ func TestError(t *testing.T) { }) jsonBytes, _ := json.Marshal(err) - assert.Equal(t, "{\"meta\":\"some data\",\"error\":\"test error\"}", string(jsonBytes)) + assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) err.SetMeta(H{ "status": "200", @@ -91,7 +90,7 @@ Error #03: third H{"error": "third", "status": "400"}, }) jsonBytes, _ := json.Marshal(errs) - assert.Equal(t, "[{\"error\":\"first\"},{\"meta\":\"some data\",\"error\":\"second\"},{\"status\":\"400\",\"error\":\"third\"}]", string(jsonBytes)) + assert.Equal(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) errs = errorMsgs{ {Err: errors.New("first"), Type: ErrorTypePrivate}, } diff --git a/render/json.go b/render/json.go index 3896740..17e6ac8 100644 --- a/render/json.go +++ b/render/json.go @@ -8,9 +8,11 @@ import ( "bytes" "net/http" - json "github.com/json-iterator/go" + "github.com/json-iterator/go" ) +var json = jsoniter.ConfigCompatibleWithStandardLibrary + type JSON struct { Data interface{} } From bf9758ca05e47a74d55995ef62042a1c62cbb53c Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sun, 9 Jul 2017 01:54:43 +0800 Subject: [PATCH 051/912] Add SecureJSON doc --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index eda1dd7..cc97c7a 100644 --- a/README.md +++ b/README.md @@ -559,6 +559,29 @@ func main() { } ``` +#### SecureJSON + +Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to response body if the given struct is array values. + +```go +func main() { + r := gin.Default() + + // You can also use your own secure json prefix + // r.SecureJsonPrefix(")]}',\n") + + r.GET("/someJSON", func(c *gin.Context) { + names := []string{"lena", "austin", "foo"} + + // Will output : while(1);["lena","austin","foo"] + c.SecureJSON(http.StatusOK, names) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + ### Serving static files ```go From df3b79e805233f553c0495cfa1fb76766b9d80b3 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Sun, 9 Jul 2017 11:02:44 +0200 Subject: [PATCH 052/912] docs(readme): add binding html checkbox example, close #129 (#994) --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index cc97c7a..05b696d 100644 --- a/README.md +++ b/README.md @@ -481,6 +481,52 @@ func startPage(c *gin.Context) { } ``` +### Bind HTML checkboxes + +See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) + +main.go + +```go +... + +type myForm struct { + Colors []string `form:"colors[]"` +} + +... + +func formHandler(c *gin.Context) { + var fakeForm myForm + c.Bind(&fakeForm) + c.JSON(200, gin.H{"color": fakeForm.Colors}) +} + +... + +``` + +form.html + +```html +

+

Check some colors

+ + + + + + + +
+``` + +result: + +``` +{"color":["red","green","blue"]} +``` + ### Multipart/Urlencoded binding ```go From 8436a9d829f22f56cbc059e06a68d19163161391 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Mon, 10 Jul 2017 14:45:19 +0800 Subject: [PATCH 053/912] fix(render): remove repeated static check. (#998) --- render/render.go | 1 - 1 file changed, 1 deletion(-) diff --git a/render/render.go b/render/render.go index 620b0d8..7185236 100644 --- a/render/render.go +++ b/render/render.go @@ -24,7 +24,6 @@ var ( _ HTMLRender = HTMLProduction{} _ Render = YAML{} _ Render = MsgPack{} - _ Render = MsgPack{} ) func writeContentType(w http.ResponseWriter, value []string) { From b6256dbe0ce7b14e1b8da3abbc710c01ac56c658 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Mon, 10 Jul 2017 08:57:09 +0200 Subject: [PATCH 054/912] chore(vendor): update jsonite rev, close #991 (#992) --- vendor/vendor.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 547bb2e..cb752e9 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -34,10 +34,10 @@ "revisionTime": "2017-06-01T23:02:30Z" }, { - "checksumSHA1": "gWQ2ncPI6qpTwS3e6/ShPwUP1uo=", + "checksumSHA1": "YY//OXgGrJOsiR8Cz1xexk/zwkc=", "path": "github.com/json-iterator/go", - "revision": "b1afefe0580e6e818dd50da9593f477c80ccd67d", - "revisionTime": "2017-07-07T13:43:33Z" + "revision": "37ba1b32b5ed52dfaff0fe50d923d210c11c8daf", + "revisionTime": "2017-07-08T17:01:13Z" }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", From fb7448f081196b9c86cade4f58b663cab2207025 Mon Sep 17 00:00:00 2001 From: whirosan Date: Mon, 10 Jul 2017 17:33:35 +0900 Subject: [PATCH 055/912] feat(binding): add UseNumber() in gin.Context.BindJSON() (#997) close #368 * resolve #368 add option to UseNumber() in gin.Context.BindJSON() * add test --- binding/json.go | 8 +++++++- mode.go | 4 ++++ mode_test.go | 7 +++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/binding/json.go b/binding/json.go index 20aae8c..215fa9b 100644 --- a/binding/json.go +++ b/binding/json.go @@ -10,7 +10,10 @@ import ( "github.com/json-iterator/go" ) -var json = jsoniter.ConfigCompatibleWithStandardLibrary +var ( + json = jsoniter.ConfigCompatibleWithStandardLibrary + EnableDecoderUseNumber = false +) type jsonBinding struct{} @@ -20,6 +23,9 @@ func (jsonBinding) Name() string { func (jsonBinding) Bind(req *http.Request, obj interface{}) error { decoder := json.NewDecoder(req.Body) + if EnableDecoderUseNumber { + decoder.UseNumber() + } if err := decoder.Decode(obj); err != nil { return err } diff --git a/mode.go b/mode.go index e24dbdc..b0d2c27 100644 --- a/mode.go +++ b/mode.go @@ -64,6 +64,10 @@ func DisableBindValidation() { binding.Validator = nil } +func EnableJsonDecoderUseNumber() { + binding.EnableDecoderUseNumber = true +} + func Mode() string { return modeName } diff --git a/mode_test.go b/mode_test.go index 0a9cbc3..f3b88a1 100644 --- a/mode_test.go +++ b/mode_test.go @@ -8,6 +8,7 @@ import ( "os" "testing" + "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" ) @@ -34,3 +35,9 @@ func TestSetMode(t *testing.T) { assert.Panics(t, func() { SetMode("unknown") }) } + +func TestEnableJsonDecoderUseNumber(t *testing.T) { + assert.False(t, binding.EnableDecoderUseNumber) + EnableJsonDecoderUseNumber() + assert.True(t, binding.EnableDecoderUseNumber) +} From e31cbdf241b1ff161e3bc5eb4af1c9601fbb7639 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 10 Jul 2017 03:41:20 -0500 Subject: [PATCH 056/912] feat(logger): show query string in logger. (#999) close #988 Signed-off-by: Bo-Yi Wu --- logger.go | 5 +++++ logger_test.go | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/logger.go b/logger.go index a03cde6..470e4bb 100644 --- a/logger.go +++ b/logger.go @@ -77,6 +77,7 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { // Start timer start := time.Now() path := c.Request.URL.Path + raw := c.Request.URL.RawQuery // Process request c.Next() @@ -97,6 +98,10 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { } comment := c.Errors.ByType(ErrorTypePrivate).String() + if raw != "" { + path = path + "?" + raw + } + fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %s %-7s %s\n%s", end.Format("2006/01/02 - 15:04:05"), statusColor, statusCode, reset, diff --git a/logger_test.go b/logger_test.go index 15d6ee9..62c1366 100644 --- a/logger_test.go +++ b/logger_test.go @@ -28,10 +28,11 @@ func TestLogger(t *testing.T) { router.HEAD("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {}) - performRequest(router, "GET", "/example") + performRequest(router, "GET", "/example?a=100") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") // I wrote these first (extending the above) but then realized they are more // like integration tests because they test the whole logging process rather From 44da058aa09ea4241c37d8f92b6ac6225a4b1aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 10 Jul 2017 22:21:42 +0800 Subject: [PATCH 057/912] add favicon example (#1001) --- examples/favicon/favicon.ico | Bin 0 -> 1150 bytes examples/favicon/main.go | 15 +++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 examples/favicon/favicon.ico create mode 100644 examples/favicon/main.go diff --git a/examples/favicon/favicon.ico b/examples/favicon/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3959cd7f9b13333df2f132f736c5f1d562278895 GIT binary patch literal 1150 zcmZvaZAep57{~u-FK%{oZPV!zqumdgW0lj0f*e`-B@(PiL9v7&C`hOvv*<%AsnCmM z1{IQ-Wf{?n%U;YZA+mQGi`IuSL&;vX7hRjRdwR}RV$i+(&bjCOpZ|03bM6B$x{XHA zXu~=U5Dy@Qpe@>o?9&907Ar*vum3Z+TrJ4TYEn6!ZB~b)J=Nj3J|jKdZO+d>z?Lj& z_>Uv9Ww+Nrr5c6J){<rb+kFDFx&2;ZqPs+vPbDNJxiNe8wtw=N&0AqM zcOowC@WP1`Pd?LV&T_`u9s(V?#9GE$d$rm#++a9y!(ypRwpflKCMItzha??@B<-87 z(;f9PR?mT^uRv=S;09x7Del60;CM)-s^@zB;Z{kiHbqD84*MC~Los%yRyWo#)=b{h z#QD3xRReH|V%h}LfOCC7GksU{PmQKwjaDo2mXP-%)qNq6u}&Y*MP9+}6F@NoTK@R2 zP_C|fe|5$>T2-osD8^2c?j<~Pfu3)`8}jxNo_zpKI7zsP<59r#D-m79ynF#Xumbn# z{X@j3OvZlrfgou=h@NW3g#Qq6m8hUU|CWjodXQ=udBa%0jlhHneqtnDl9WM7;#`tK zJSwVhe?o%ri~4U8^*X)&Xh zx9Du{kiWw?dGcUb7csINYBHJn)mG~eoK8P-ayVQrWot$TR|xKKe11%47^~HG!({SM zZ$F)pmNxuOXf7B3o{^zsFId1abLIf$23D`;g80HheyyN@^XzQDUzU9OoZinxyEuqKAfmpL|X4q(TQVDg3yLC>a53eU?Md^Kmz%hLJtUsn|s zegCv^qr!`egXgQOL#DDaWy~9SFd{Xz2M&iX)o%ZKv$T|#)YK?vcJ2(^FE0&)6%-lPA8Q&?sB<0v3N1ZvSkD1^X3h2@%dik{d=zVczBH0%jvIh>Q6<# zXwkPLxw(DI3kp7ra|274KB09EcI_HTuB;3gtEvM3=#7mZP**pKy?X=J%F5yR+S;FE kdSEK>WfFCrj=Hk~C=--X$BaX)h1R8x#EIB`qMMHYHxij({r~^~ literal 0 HcmV?d00001 diff --git a/examples/favicon/main.go b/examples/favicon/main.go new file mode 100644 index 0000000..5ad3933 --- /dev/null +++ b/examples/favicon/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/gin-gonic/gin" + "github.com/thinkerou/favicon" +) + +func main() { + app := gin.Default() + app.Use(favicon.New("./favicon.ico")) + app.GET("/ping", func(c *gin.Context) { + c.String(200, "Hello favicon.") + }) + app.Run(":8080") +} From 87fdff8f88e22722ce258ebb46e5285341ebe523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 11 Jul 2017 09:03:09 +0800 Subject: [PATCH 058/912] [docs] add http2 example (#1000) --- examples/http2/README.md | 18 +++++++++++++++++ examples/http2/main.go | 32 ++++++++++++++++++++++++++++++ examples/http2/testdata/ca.pem | 15 ++++++++++++++ examples/http2/testdata/server.key | 16 +++++++++++++++ examples/http2/testdata/server.pem | 16 +++++++++++++++ 5 files changed, 97 insertions(+) create mode 100644 examples/http2/README.md create mode 100644 examples/http2/main.go create mode 100644 examples/http2/testdata/ca.pem create mode 100644 examples/http2/testdata/server.key create mode 100644 examples/http2/testdata/server.pem diff --git a/examples/http2/README.md b/examples/http2/README.md new file mode 100644 index 0000000..42dd4b8 --- /dev/null +++ b/examples/http2/README.md @@ -0,0 +1,18 @@ +## How to generate RSA private key and digital certificate + +1. Install Openssl + +Please visit https://github.com/openssl/openssl to get pkg and install. + +2. Generate RSA private key + +```sh +$ mkdir testdata +$ openssl genrsa -out ./testdata/server.key 2048 +``` + +3. Generate digital certificate + +```sh +$ openssl req -new -x509 -key ./testdata/server.key -out ./testdata/server.pem -days 365 +``` diff --git a/examples/http2/main.go b/examples/http2/main.go new file mode 100644 index 0000000..19e65f8 --- /dev/null +++ b/examples/http2/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "html/template" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.SetHTMLTemplate(html) + + r.GET("/welcome", func(c *gin.Context) { + c.HTML(200, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} diff --git a/examples/http2/testdata/ca.pem b/examples/http2/testdata/ca.pem new file mode 100644 index 0000000..6c8511a --- /dev/null +++ b/examples/http2/testdata/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla +Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 +YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT +BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 ++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu +g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd +Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau +sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m +oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG +Dfcog5wrJytaQ6UA0wE= +-----END CERTIFICATE----- diff --git a/examples/http2/testdata/server.key b/examples/http2/testdata/server.key new file mode 100644 index 0000000..143a5b8 --- /dev/null +++ b/examples/http2/testdata/server.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD +M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf +3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY +AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm +V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY +tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p +dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q +K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR +81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff +DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd +aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 +ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 +XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe +F98XJ7tIFfJq +-----END PRIVATE KEY----- diff --git a/examples/http2/testdata/server.pem b/examples/http2/testdata/server.pem new file mode 100644 index 0000000..f3d43fc --- /dev/null +++ b/examples/http2/testdata/server.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET +MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx +MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV +BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 +ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco +LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg +zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd +9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw +CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy +em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G +CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 +hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh +y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 +-----END CERTIFICATE----- From 02a6f9b6bc8ffba2e8b55ca78d5a9448ea8aef1e Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 11 Jul 2017 13:59:03 +0800 Subject: [PATCH 059/912] chore(vendor): update json-iterator revison to fix TestRenderIndentedJSON failed (#1003) * update json-iterator revison to fix TestRenderIndentedJSON failed * fix(test): add space between key and value as same as standard JSON. * fix(test): add space between key and value as same as standard JSON. --- context_test.go | 2 +- vendor/vendor.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index f57fa6f..758fecd 100644 --- a/context_test.go +++ b/context_test.go @@ -582,7 +582,7 @@ func TestContextRenderIndentedJSON(t *testing.T) { c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) assert.Equal(t, w.Code, 201) - assert.Equal(t, "{\n \"bar\":\"foo\",\n \"foo\":\"bar\",\n \"nested\":{\n \"foo\":\"bar\"\n }\n}", w.Body.String()) + assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } diff --git a/vendor/vendor.json b/vendor/vendor.json index cb752e9..3af3cb5 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -34,10 +34,10 @@ "revisionTime": "2017-06-01T23:02:30Z" }, { - "checksumSHA1": "YY//OXgGrJOsiR8Cz1xexk/zwkc=", + "checksumSHA1": "bgb/lk2wroBJ5z+JI2xnVj7WkwI=", "path": "github.com/json-iterator/go", - "revision": "37ba1b32b5ed52dfaff0fe50d923d210c11c8daf", - "revisionTime": "2017-07-08T17:01:13Z" + "revision": "845d8438db34cc782608bbee7647a522b4e87de0", + "revisionTime": "2017-07-10T17:07:18Z" }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", From aa6d2d29f8ac39c365aa5b72fdec01029b0d5264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 11 Jul 2017 23:28:08 +0800 Subject: [PATCH 060/912] refactor(doc): use space not tab (#1006) --- context.go | 63 +++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/context.go b/context.go index acf2cd2..198dd3e 100644 --- a/context.go +++ b/context.go @@ -85,8 +85,8 @@ func (c *Context) Copy() *Context { return &cp } -// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", this -// function will return "main.handleGetUsers" +// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", +// this function will return "main.handleGetUsers" func (c *Context) HandlerName() string { return nameOfFunction(c.handlers.Last()) } @@ -117,8 +117,8 @@ func (c *Context) IsAborted() bool { } // Abort prevents pending handlers from being called. Note that this will not stop the current handler. -// Let's say you have an authorization middleware that validates that the current request is authorized. If the -// authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers +// Let's say you have an authorization middleware that validates that the current request is authorized. +// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers // for this request are not called. func (c *Context) Abort() { c.index = abortIndex @@ -132,15 +132,16 @@ func (c *Context) AbortWithStatus(code int) { c.Abort() } -// AbortWithStatusJSON calls `Abort()` and then `JSON` internally. This method stops the chain, writes the status code and return a JSON body +// AbortWithStatusJSON calls `Abort()` and then `JSON` internally. +// This method stops the chain, writes the status code and return a JSON body. // It also sets the Content-Type as "application/json". func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) { c.Abort() c.JSON(code, jsonObj) } -// AbortWithError calls `AbortWithStatus()` and `Error()` internally. This method stops the chain, writes the status code and -// pushes the specified error to `c.Errors`. +// AbortWithError calls `AbortWithStatus()` and `Error()` internally. +// This method stops the chain, writes the status code and pushes the specified error to `c.Errors`. // See Context.Error() for more details. func (c *Context) AbortWithError(code int, err error) *Error { c.AbortWithStatus(code) @@ -153,8 +154,8 @@ func (c *Context) AbortWithError(code int, err error) *Error { // Error attaches an error to the current context. The error is pushed to a list of errors. // It's a good idea to call Error for each error that occurred during the resolution of a request. -// A middleware can be used to collect all the errors -// and push them to a database together, print a log, or append it in the HTTP response. +// A middleware can be used to collect all the errors and push them to a database together, +// print a log, or append it in the HTTP response. // Error will panic if err is nil. func (c *Context) Error(err error) *Error { if err == nil { @@ -296,10 +297,10 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) // Param returns the value of the URL param. // It is a shortcut for c.Params.ByName(key) -// router.GET("/user/:id", func(c *gin.Context) { -// // a GET request to /user/john -// id := c.Param("id") // id == "john" -// }) +// router.GET("/user/:id", func(c *gin.Context) { +// // a GET request to /user/john +// id := c.Param("id") // id == "john" +// }) func (c *Context) Param(key string) string { return c.Params.ByName(key) } @@ -307,11 +308,11 @@ func (c *Context) Param(key string) string { // Query returns the keyed url query value if it exists, // otherwise it returns an empty string `("")`. // It is shortcut for `c.Request.URL.Query().Get(key)` -// GET /path?id=1234&name=Manu&value= -// c.Query("id") == "1234" -// c.Query("name") == "Manu" -// c.Query("value") == "" -// c.Query("wtf") == "" +// GET /path?id=1234&name=Manu&value= +// c.Query("id") == "1234" +// c.Query("name") == "Manu" +// c.Query("value") == "" +// c.Query("wtf") == "" func (c *Context) Query(key string) string { value, _ := c.GetQuery(key) return value @@ -320,10 +321,10 @@ func (c *Context) Query(key string) string { // DefaultQuery returns the keyed url query value if it exists, // otherwise it returns the specified defaultValue string. // See: Query() and GetQuery() for further information. -// GET /?name=Manu&lastname= -// c.DefaultQuery("name", "unknown") == "Manu" -// c.DefaultQuery("id", "none") == "none" -// c.DefaultQuery("lastname", "none") == "" +// GET /?name=Manu&lastname= +// c.DefaultQuery("name", "unknown") == "Manu" +// c.DefaultQuery("id", "none") == "none" +// c.DefaultQuery("lastname", "none") == "" func (c *Context) DefaultQuery(key, defaultValue string) string { if value, ok := c.GetQuery(key); ok { return value @@ -335,10 +336,10 @@ func (c *Context) DefaultQuery(key, defaultValue string) string { // if it exists `(value, true)` (even when the value is an empty string), // otherwise it returns `("", false)`. // It is shortcut for `c.Request.URL.Query().Get(key)` -// GET /?name=Manu&lastname= -// ("Manu", true) == c.GetQuery("name") -// ("", false) == c.GetQuery("id") -// ("", true) == c.GetQuery("lastname") +// GET /?name=Manu&lastname= +// ("Manu", true) == c.GetQuery("name") +// ("", false) == c.GetQuery("id") +// ("", true) == c.GetQuery("lastname") func (c *Context) GetQuery(key string) (string, bool) { if values, ok := c.GetQueryArray(key); ok { return values[0], ok @@ -384,9 +385,9 @@ func (c *Context) DefaultPostForm(key, defaultValue string) string { // form or multipart form when it exists `(value, true)` (even when the value is an empty string), // otherwise it returns ("", false). // For example, during a PATCH request to update the user's email: -// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com" -// email= --> ("", true) := GetPostForm("email") // set email to "" -// --> ("", false) := GetPostForm("email") // do nothing with email +// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com" +// email= --> ("", true) := GetPostForm("email") // set email to "" +// --> ("", false) := GetPostForm("email") // do nothing with email func (c *Context) GetPostForm(key string) (string, bool) { if values, ok := c.GetPostFormArray(key); ok { return values[0], ok @@ -432,8 +433,8 @@ func (c *Context) MultipartForm() (*multipart.Form, error) { // Bind checks the Content-Type to select a binding engine automatically, // Depending the "Content-Type" header different bindings are used: -// "application/json" --> JSON binding -// "application/xml" --> XML binding +// "application/json" --> JSON binding +// "application/xml" --> XML binding // otherwise --> returns an error // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. From bbd4dfee5056087c640a75c6cc21567f4f47585d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 12 Jul 2017 15:01:46 +0800 Subject: [PATCH 061/912] add warning using http2 example (#1009) --- examples/http2/main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/http2/main.go b/examples/http2/main.go index 19e65f8..07df01e 100644 --- a/examples/http2/main.go +++ b/examples/http2/main.go @@ -2,6 +2,8 @@ package main import ( "html/template" + "log" + "os" "github.com/gin-gonic/gin" ) @@ -18,6 +20,9 @@ var html = template.Must(template.New("https").Parse(` `)) func main() { + logger := log.New(os.Stderr, "", 0) + logger.Println("[WARNING] DON'T USE THE EMBED CERTS FROM THIS EXAMPLE IN PRODUCTION ENVIRONMENT, GENERATE YOUR OWN!") + r := gin.Default() r.SetHTMLTemplate(html) From 986049d24c3980e773b15ba271de58046f16200b Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 12 Jul 2017 04:26:58 -0500 Subject: [PATCH 062/912] chore(vendor): upgrade jsonite rev (#1011) Signed-off-by: Bo-Yi Wu --- vendor/vendor.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 3af3cb5..822e201 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -34,10 +34,10 @@ "revisionTime": "2017-06-01T23:02:30Z" }, { - "checksumSHA1": "bgb/lk2wroBJ5z+JI2xnVj7WkwI=", + "checksumSHA1": "1K394KhUNPplCZzaz69+az17fR0=", "path": "github.com/json-iterator/go", - "revision": "845d8438db34cc782608bbee7647a522b4e87de0", - "revisionTime": "2017-07-10T17:07:18Z" + "revision": "dc388588a371d252dabef1f758e53a958fa11726", + "revisionTime": "2017-07-12T08:40:14Z" }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", From 65a6dd46a50bd435597b5bcababd1b2a811c9073 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Wed, 12 Jul 2017 13:02:00 +0200 Subject: [PATCH 063/912] chore(vendor): update jsonite rev, #1010 (#1012) --- vendor/vendor.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 822e201..29141c4 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -34,10 +34,10 @@ "revisionTime": "2017-06-01T23:02:30Z" }, { - "checksumSHA1": "1K394KhUNPplCZzaz69+az17fR0=", + "checksumSHA1": "WFJPa8cL6nzQU3yA1iN+gmaqrSU=", "path": "github.com/json-iterator/go", - "revision": "dc388588a371d252dabef1f758e53a958fa11726", - "revisionTime": "2017-07-12T08:40:14Z" + "revision": "4b33139ad07fda872cb378bb4218b2fab74ce62b", + "revisionTime": "2017-07-12T09:56:51Z" }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", From fe4d405108edbe697305be4bfa86ad8fde64a975 Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Fri, 14 Jul 2017 18:52:16 +0200 Subject: [PATCH 064/912] docs(readme): fix single file example (#1017) issue discovered by @sgon00 at gitter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05b696d..d4dcb9d 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ func main() { file, _ := c.FormFile("file") log.Println(file.Filename) - c.String(http.StatusOK, fmt.Printf("'%s' uploaded!", file.Filename)) + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) }) router.Run(":8080") } From 5cb25a6410a299dcc31e7f31b41c60c90e30b3f6 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sat, 15 Jul 2017 01:28:16 +0800 Subject: [PATCH 065/912] docs(readme): fix multiple file example (#1018) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4dcb9d..86001ae 100644 --- a/README.md +++ b/README.md @@ -305,7 +305,7 @@ func main() { for _, file := range files { log.Println(file.Filename) } - c.String(http.StatusOK, fmt.Printf("%d files uploaded!", len(files))) + c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) }) router.Run(":8080") } From 93b3a0d7ec95c33dc327397ab1756c36853328ee Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sun, 16 Jul 2017 11:42:08 +0800 Subject: [PATCH 066/912] feat(context): add SaveUploadedFile func. (#1022) * feat(context): add SaveUploadedFile func. * feat(context): update multiple upload examples. * style(example): fix gofmt * fix(example): add missing return --- README.md | 8 ++++- context.go | 19 ++++++++++++ context_test.go | 42 +++++++++++++++++++++++++++ examples/upload-file/multiple/main.go | 20 ++----------- examples/upload-file/single/main.go | 18 ++---------- 5 files changed, 72 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 86001ae..ccc5bcc 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,10 @@ func main() { // single file file, _ := c.FormFile("file") log.Println(file.Filename) - + + // Upload the file to specific dst. + // c.SaveUploadedFile(file, dst) + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) }) router.Run(":8080") @@ -304,6 +307,9 @@ func main() { for _, file := range files { log.Println(file.Filename) + + // Upload the file to specific dst. + // c.SaveUploadedFile(file, dst) } c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) }) diff --git a/context.go b/context.go index 198dd3e..f29464d 100644 --- a/context.go +++ b/context.go @@ -13,6 +13,7 @@ import ( "net" "net/http" "net/url" + "os" "strings" "time" @@ -431,6 +432,24 @@ func (c *Context) MultipartForm() (*multipart.Form, error) { return c.Request.MultipartForm, err } +// SaveUploadedFile uploads the form file to specific dst. +func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error { + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + io.Copy(out, src) + return nil +} + // Bind checks the Content-Type to select a binding engine automatically, // Depending the "Content-Type" header different bindings are used: // "application/json" --> JSON binding diff --git a/context_test.go b/context_test.go index 758fecd..db960fb 100644 --- a/context_test.go +++ b/context_test.go @@ -72,12 +72,18 @@ func TestContextFormFile(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, "test", f.Filename) } + + assert.NoError(t, c.SaveUploadedFile(f, "test")) } func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) mw.WriteField("foo", "bar") + w, err := mw.CreateFormFile("file", "test") + if assert.NoError(t, err) { + w.Write([]byte("test")) + } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) @@ -86,6 +92,42 @@ func TestContextMultipartForm(t *testing.T) { if assert.NoError(t, err) { assert.NotNil(t, f) } + + assert.NoError(t, c.SaveUploadedFile(f.File["file"][0], "test")) +} + +func TestSaveUploadedOpenFailed(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.Close() + + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + + f := &multipart.FileHeader{ + Filename: "file", + } + assert.Error(t, c.SaveUploadedFile(f, "test")) +} + +func TestSaveUploadedCreateFailed(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + w, err := mw.CreateFormFile("file", "test") + if assert.NoError(t, err) { + w.Write([]byte("test")) + } + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + f, err := c.FormFile("file") + if assert.NoError(t, err) { + assert.Equal(t, "test", f.Filename) + } + + assert.Error(t, c.SaveUploadedFile(f, "/")) } func TestContextReset(t *testing.T) { diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go index 2258834..4bb4cdc 100644 --- a/examples/upload-file/multiple/main.go +++ b/examples/upload-file/multiple/main.go @@ -2,9 +2,7 @@ package main import ( "fmt" - "io" "net/http" - "os" "github.com/gin-gonic/gin" ) @@ -25,24 +23,10 @@ func main() { files := form.File["files"] for _, file := range files { - // Source - src, err := file.Open() - if err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("file open err: %s", err.Error())) + if err := c.SaveUploadedFile(file, file.Filename); err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } - defer src.Close() - - // Destination - dst, err := os.Create(file.Filename) - if err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("Create file err: %s", err.Error())) - return - } - defer dst.Close() - - // Copy - io.Copy(dst, src) } c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email)) diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go index 1e9596c..372a299 100644 --- a/examples/upload-file/single/main.go +++ b/examples/upload-file/single/main.go @@ -2,9 +2,7 @@ package main import ( "fmt" - "io" "net/http" - "os" "github.com/gin-gonic/gin" ) @@ -22,23 +20,11 @@ func main() { c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) return } - src, err := file.Open() - if err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("file open err: %s", err.Error())) - return - } - defer src.Close() - // Destination - dst, err := os.Create(file.Filename) - if err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("Create file err: %s", err.Error())) + if err := c.SaveUploadedFile(file, file.Filename); err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } - defer dst.Close() - - // Copy - io.Copy(dst, src) c.String(http.StatusOK, fmt.Sprintf("File %s uploaded successfully with fields name=%s and email=%s.", file.Filename, name, email)) }) From 8678b3df962ee52c8795802b4bee3c439cc9aeda Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Mon, 17 Jul 2017 02:50:45 +0200 Subject: [PATCH 067/912] docs(readme): add reference to validator.v8 docs, close #738 (#1024) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ccc5bcc..211ed59 100644 --- a/README.md +++ b/README.md @@ -406,6 +406,8 @@ func main() { To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz). +Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags). + Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. 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. From 8f861946b028a0b5ed4acf8df4b673e23e8c5634 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Mon, 17 Jul 2017 18:57:59 +0800 Subject: [PATCH 068/912] style(msgpack): remove redundant comments (#1027) --- binding/msgpack.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/binding/msgpack.go b/binding/msgpack.go index 6936717..7faea4b 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -17,12 +17,8 @@ func (msgpackBinding) Name() string { } func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { - if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil { - //var decoder *codec.Decoder = codec.NewDecoder(req.Body, &codec.MsgpackHandle) - //if err := decoder.Decode(&obj); err != nil { return err } return validate(obj) - } From b539606eedd1eabd650b2284394a8ada992fd411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 18 Jul 2017 08:54:38 +0800 Subject: [PATCH 069/912] use return not use else (#1028) --- auth.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/auth.go b/auth.go index 7ebf557..75a7c89 100644 --- a/auth.go +++ b/auth.go @@ -52,11 +52,13 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc { // Credentials doesn't match, we return 401 and abort handlers chain. c.Header("WWW-Authenticate", realm) c.AbortWithStatus(401) - } else { - // The user credentials was found, set user's id to key AuthUserKey in this context, the userId can be read later using - // c.MustGet(gin.AuthUserKey) - c.Set(AuthUserKey, user) + return } + + // The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using + // c.MustGet(gin.AuthUserKey) + c.Set(AuthUserKey, user) + return } } From 199bbf2ae55c3601425895a0e4a760599db3c6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 18 Jul 2017 09:11:53 +0800 Subject: [PATCH 070/912] refactor(gin): use return not use else for reducing indent (#1031) --- gin.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gin.go b/gin.go index 8388ac7..fc9214d 100644 --- a/gin.go +++ b/gin.go @@ -156,19 +156,21 @@ func (engine *Engine) LoadHTMLGlob(pattern string) { if IsDebugging() { debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern))) engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} - } else { - templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)) - engine.SetHTMLTemplate(templ) + return } + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)) + engine.SetHTMLTemplate(templ) + return } func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} - } else { - templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) - engine.SetHTMLTemplate(templ) + return } + templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) + engine.SetHTMLTemplate(templ) + return } func (engine *Engine) SetHTMLTemplate(templ *template.Template) { From 30cfa590bb2b851d521da0198d3803eca2cc2a4b Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 18 Jul 2017 09:23:10 +0800 Subject: [PATCH 071/912] dos(upload): fix alignment (#1030) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 211ed59..7e3df99 100644 --- a/README.md +++ b/README.md @@ -276,8 +276,8 @@ func main() { file, _ := c.FormFile("file") log.Println(file.Filename) - // Upload the file to specific dst. - // c.SaveUploadedFile(file, dst) + // Upload the file to specific dst. + // c.SaveUploadedFile(file, dst) c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) }) @@ -309,7 +309,7 @@ func main() { 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))) }) From 7180f2ba62d6aa9c81fafbac409a07ebeaa26fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 18 Jul 2017 11:39:39 +0800 Subject: [PATCH 072/912] not use tmp var (#1032) --- context.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/context.go b/context.go index f29464d..306e205 100644 --- a/context.go +++ b/context.go @@ -358,8 +358,7 @@ func (c *Context) QueryArray(key string) []string { // GetQueryArray returns a slice of strings for a given query key, plus // a boolean value whether at least one value exists for the given key. func (c *Context) GetQueryArray(key string) ([]string, bool) { - req := c.Request - if values, ok := req.URL.Query()[key]; ok && len(values) > 0 { + if values, ok := c.Request.URL.Query()[key]; ok && len(values) > 0 { return values, true } return []string{}, false From ce670a64977a513d8caa58c143471ddbbf641bd4 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 18 Jul 2017 16:01:29 -0500 Subject: [PATCH 073/912] refactor(json): make jsonite optional with build tags (#1026) * refactor(json): Restore gin support for app engine Create new folder to support multiple json package. restore gin support for app engine (disable jsonite through tags) use jsoniter $ go build -tags=jsoniter . use default json $ go build . Signed-off-by: Bo-Yi Wu * rename json file. Signed-off-by: Bo-Yi Wu * docs(json): add build tags document. * fix(docs): markdown format. * fix(json): missing space. --- README.md | 9 ++++++++- binding/json.go | 3 +-- errors.go | 4 +--- errors_test.go | 1 + json/json.go | 17 +++++++++++++++++ json/jsoniter.go | 18 ++++++++++++++++++ render/json.go | 4 +--- 7 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 json/json.go create mode 100644 json/jsoniter.go diff --git a/README.md b/README.md index 7e3df99..76ab3b0 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,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: @@ -141,6 +140,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 diff --git a/binding/json.go b/binding/json.go index 215fa9b..f600a5f 100644 --- a/binding/json.go +++ b/binding/json.go @@ -7,11 +7,10 @@ package binding import ( "net/http" - "github.com/json-iterator/go" + "github.com/gin-gonic/gin/json" ) var ( - json = jsoniter.ConfigCompatibleWithStandardLibrary EnableDecoderUseNumber = false ) diff --git a/errors.go b/errors.go index 26d4e47..1976110 100644 --- a/errors.go +++ b/errors.go @@ -9,11 +9,9 @@ import ( "fmt" "reflect" - "github.com/json-iterator/go" + "github.com/gin-gonic/gin/json" ) -var json = jsoniter.ConfigCompatibleWithStandardLibrary - type ErrorType uint64 const ( diff --git a/errors_test.go b/errors_test.go index a0d9550..5e596af 100644 --- a/errors_test.go +++ b/errors_test.go @@ -8,6 +8,7 @@ import ( "errors" "testing" + "github.com/gin-gonic/gin/json" "github.com/stretchr/testify/assert" ) diff --git a/json/json.go b/json/json.go new file mode 100644 index 0000000..d2d0f8b --- /dev/null +++ b/json/json.go @@ -0,0 +1,17 @@ +// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !jsoniter + +package json + +import ( + "encoding/json" +) + +var ( + Marshal = json.Marshal + MarshalIndent = json.MarshalIndent + NewDecoder = json.NewDecoder +) diff --git a/json/jsoniter.go b/json/jsoniter.go new file mode 100644 index 0000000..65deee5 --- /dev/null +++ b/json/jsoniter.go @@ -0,0 +1,18 @@ +// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build jsoniter + +package json + +import ( + "github.com/json-iterator/go" +) + +var ( + json = jsoniter.ConfigCompatibleWithStandardLibrary + Marshal = json.Marshal + MarshalIndent = json.MarshalIndent + NewDecoder = json.NewDecoder +) diff --git a/render/json.go b/render/json.go index 17e6ac8..eb2548e 100644 --- a/render/json.go +++ b/render/json.go @@ -8,11 +8,9 @@ import ( "bytes" "net/http" - "github.com/json-iterator/go" + "github.com/gin-gonic/gin/json" ) -var json = jsoniter.ConfigCompatibleWithStandardLibrary - type JSON struct { Data interface{} } From 88566b928cabbe45f0e6e9ae4f8fad97fc8ae9d4 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 18 Jul 2017 16:06:14 -0500 Subject: [PATCH 074/912] chore(vendor): update jsoniter package (#1033) --- vendor/vendor.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 29141c4..e8690a2 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -34,10 +34,10 @@ "revisionTime": "2017-06-01T23:02:30Z" }, { - "checksumSHA1": "WFJPa8cL6nzQU3yA1iN+gmaqrSU=", + "checksumSHA1": "0e59uuETpidkmpaRwipQ8auqwhM=", "path": "github.com/json-iterator/go", - "revision": "4b33139ad07fda872cb378bb4218b2fab74ce62b", - "revisionTime": "2017-07-12T09:56:51Z" + "revision": "6b6938829d6156d7b9825f83eec757f0f571c981", + "revisionTime": "2017-07-18T14:19:52Z" }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", From 74221b8a35319c6aaf1b7826069fb19302e3276e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 19 Jul 2017 02:40:05 -0500 Subject: [PATCH 075/912] docs(benchmark): update benchmark data (#1035) * feat(Benchmark): update benchmark data * fix format. Signed-off-by: Bo-Yi Wu * docs(benchmarks): re indent column, feature gin * docs(benchmark): fix newline on vm details * docs(readme): beautify bench table, explain result --- BENCHMARKS.md | 896 +++++++++++++++++++++++++++++++++----------------- README.md | 69 ++-- 2 files changed, 635 insertions(+), 330 deletions(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 6efe3ca..9a7df86 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 + +``` +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 ``` -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 +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/README.md b/README.md index 76ab3b0..07fc960 100644 --- a/README.md +++ b/README.md @@ -44,41 +44,40 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr [See all benchmarks](/BENCHMARKS.md) - -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) +Benchmark name | (1) | (2) | (3) | (4) +--------------------------------------------|-----------:|------------:|-----------:|---------: +**BenchmarkGin_GithubAll** | **30000** | **48375** | **0** | **0** +BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167 +BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943 +BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812 +BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453 +BenchmarkDenco_GithubAll | 10000 | 158819 | 20224 | 167 +BenchmarkEcho_GithubAll | 10000 | 154700 | 6496 | 203 +BenchmarkGocraftWeb_GithubAll | 3000 | 570806 | 131656 | 1686 +BenchmarkGoji_GithubAll | 2000 | 818034 | 56112 | 334 +BenchmarkGojiv2_GithubAll | 2000 | 1213973 | 274768 | 3712 +BenchmarkGoJsonRest_GithubAll | 2000 | 785796 | 134371 | 2737 +BenchmarkGoRestful_GithubAll | 300 | 5238188 | 689672 | 4519 +BenchmarkGorillaMux_GithubAll | 100 | 10257726 | 211840 | 2272 +BenchmarkHttpRouter_GithubAll | 20000 | 105414 | 13792 | 167 +BenchmarkHttpTreeMux_GithubAll | 10000 | 319934 | 65856 | 671 +BenchmarkKocha_GithubAll | 10000 | 209442 | 23304 | 843 +BenchmarkLARS_GithubAll | 20000 | 62565 | 0 | 0 +BenchmarkMacaron_GithubAll | 2000 | 1161270 | 204194 | 2000 +BenchmarkMartini_GithubAll | 200 | 9991713 | 226549 | 2325 +BenchmarkPat_GithubAll | 200 | 5590793 | 1499568 | 27435 +BenchmarkPossum_GithubAll | 10000 | 319768 | 84448 | 609 +BenchmarkR2router_GithubAll | 10000 | 305134 | 77328 | 979 +BenchmarkRivet_GithubAll | 10000 | 132134 | 16272 | 167 +BenchmarkTango_GithubAll | 3000 | 552754 | 63826 | 1618 +BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 | 5374 +BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848 +BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609 + +(1): Total Repetitions achieved in constant time, higher means more confident result +(2): Single Repetition Duration (ns/op), lower is better +(3): Heap Memory (B/op), lower is better +(4): Average Allocations per Repetition (allocs/op), lower is better ## Gin v1. stable From c19aa0598b6bab47f498e68ebc60ceafded8631f Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Wed, 19 Jul 2017 15:50:05 +0800 Subject: [PATCH 076/912] feat(context): add BindQuery func (#1029) * feat(context): add BindQuery func, only parse/bind the query string params. * docs(readme): add BindQuery section. * docs(readme): fix import. * docs(readme): separate import --- README.md | 38 +++++++++++++++++++++++++++++++++++++- binding/binding.go | 1 + binding/binding_test.go | 27 +++++++++++++++++++++++++++ binding/query.go | 23 +++++++++++++++++++++++ context.go | 5 +++++ context_test.go | 16 ++++++++++++++++ 6 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 binding/query.go diff --git a/README.md b/README.md index 07fc960..3585873 100644 --- a/README.md +++ b/README.md @@ -460,7 +460,43 @@ func main() { } ``` -### Bind Query String +### Only Bind Query String + +`BindQuery` 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.BindQuery(&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). diff --git a/binding/binding.go b/binding/binding.go index 1dbf246..971547c 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -39,6 +39,7 @@ var ( JSON = jsonBinding{} XML = xmlBinding{} Form = formBinding{} + Query = queryBinding{} FormPost = formPostBinding{} FormMultipart = formMultipartBinding{} ProtoBuf = protobufBinding{} diff --git a/binding/binding_test.go b/binding/binding_test.go index d7cdf77..5575e16 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -67,6 +67,18 @@ func TestBindingForm2(t *testing.T) { "", "") } +func TestBindingQuery(t *testing.T) { + testQueryBinding(t, "POST", + "/?foo=bar&bar=foo", "/", + "foo=unused", "bar2=foo") +} + +func TestBindingQuery2(t *testing.T) { + testQueryBinding(t, "GET", + "/?foo=bar&bar=foo", "/?bar2=foo", + "foo=unused", "") +} + func TestBindingXML(t *testing.T) { testBodyBinding(t, XML, "xml", @@ -204,6 +216,21 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) assert.Error(t, err) } +func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) { + b := Query + assert.Equal(t, b.Name(), "query") + + obj := FooBarStruct{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, obj.Bar, "foo") +} + func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, b.Name(), name) diff --git a/binding/query.go b/binding/query.go new file mode 100644 index 0000000..a789f79 --- /dev/null +++ b/binding/query.go @@ -0,0 +1,23 @@ +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "net/http" +) + +type queryBinding struct{} + +func (queryBinding) Name() string { + return "query" +} + +func (queryBinding) Bind(req *http.Request, obj interface{}) error { + values := req.URL.Query() + if err := mapForm(obj, values); err != nil { + return err + } + return validate(obj) +} diff --git a/context.go b/context.go index 306e205..87aa1be 100644 --- a/context.go +++ b/context.go @@ -467,6 +467,11 @@ func (c *Context) BindJSON(obj interface{}) error { return c.MustBindWith(obj, binding.JSON) } +// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query) +func (c *Context) BindQuery(obj interface{}) error { + return c.MustBindWith(obj, binding.Query) +} + // MustBindWith binds the passed struct pointer using the specified binding // engine. It will abort the request with HTTP 400 if any error ocurrs. // See the binding package. diff --git a/context_test.go b/context_test.go index db960fb..15569bf 100644 --- a/context_test.go +++ b/context_test.go @@ -1186,6 +1186,22 @@ func TestContextBindWithJSON(t *testing.T) { assert.Equal(t, w.Body.Len(), 0) } +func TestContextBindWithQuery(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + + var obj struct { + Foo string `form:"foo"` + Bar string `form:"bar"` + } + assert.NoError(t, c.BindQuery(&obj)) + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextBadAutoBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From 7b508186dd71146cbdb3d14f2c2e1aaaa51f7e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 19 Jul 2017 20:49:18 +0800 Subject: [PATCH 077/912] style: remove optional return (#1036) --- auth.go | 1 - gin.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/auth.go b/auth.go index 75a7c89..c214e21 100644 --- a/auth.go +++ b/auth.go @@ -58,7 +58,6 @@ 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.Set(AuthUserKey, user) - return } } diff --git a/gin.go b/gin.go index fc9214d..0372931 100644 --- a/gin.go +++ b/gin.go @@ -158,9 +158,9 @@ func (engine *Engine) LoadHTMLGlob(pattern string) { 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)) engine.SetHTMLTemplate(templ) - return } func (engine *Engine) LoadHTMLFiles(files ...string) { @@ -168,9 +168,9 @@ func (engine *Engine) LoadHTMLFiles(files ...string) { 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 } func (engine *Engine) SetHTMLTemplate(templ *template.Template) { From d39ed41ab3795346f5f843ec31bc0138be07eaab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 21 Jul 2017 10:29:23 +0800 Subject: [PATCH 078/912] update template example (#1038) * update template example * add template example folder --- README.md | 55 +++++++++++++++++++++++---------------- examples/template/main.go | 32 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 examples/template/main.go diff --git a/README.md b/README.md index 3585873..b14c2a2 100644 --- a/README.md +++ b/README.md @@ -791,31 +791,42 @@ You may use custom delims #### 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 diff --git a/examples/template/main.go b/examples/template/main.go new file mode 100644 index 0000000..f9e611d --- /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") +} From 7fafb3f4a177ca92821dc5bf6e59f17e5f130588 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 28 Jul 2017 08:50:58 +0800 Subject: [PATCH 079/912] doc(context): add status func comment. (#1042) --- context.go | 1 + 1 file changed, 1 insertion(+) diff --git a/context.go b/context.go index 87aa1be..35ca8d2 100644 --- a/context.go +++ b/context.go @@ -561,6 +561,7 @@ func bodyAllowedForStatus(status int) bool { return true } +// Status sets the HTTP response code. func (c *Context) Status(code int) { c.writermem.WriteHeader(code) } From df37e74fa1bbf8897ec2fa6ab347b27acdf5c444 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 1 Aug 2017 12:49:28 +0800 Subject: [PATCH 080/912] doc(context): more clearer bind doc when input is not valid (#1049) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 35ca8d2..895ba7a 100644 --- a/context.go +++ b/context.go @@ -456,7 +456,7 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error // otherwise --> returns an error // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. -// Like ParseBody() but this method also writes a 400 error if the json is not valid. +// It will writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. func (c *Context) Bind(obj interface{}) error { b := binding.Default(c.Request.Method, c.ContentType()) return c.MustBindWith(obj, b) From 4b54b862720f68d1ee16e89384e370b04e78f659 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Wed, 2 Aug 2017 23:00:10 +0800 Subject: [PATCH 081/912] fix composite literal uses unkeyed fields warnings, #1050 (#1051) --- gin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index 0372931..8347ce2 100644 --- a/gin.go +++ b/gin.go @@ -121,7 +121,7 @@ func New() *Engine { UseRawPath: false, UnescapePathValues: true, trees: make(methodTrees, 0, 9), - delims: render.Delims{"{{", "}}"}, + delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", } engine.RouterGroup.engine = engine @@ -143,7 +143,7 @@ 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 } From 81007d2ce0176f7a9ce52dd12e56edd7ef40e72c Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 4 Aug 2017 13:45:59 +0800 Subject: [PATCH 082/912] refactor(test): unify assert.Equal usage (#1054) --- auth_test.go | 20 ++++++++++---------- debug_test.go | 6 +++--- errors_test.go | 24 ++++++++++++------------ middleware_test.go | 28 ++++++++++++++-------------- response_writer_test.go | 36 ++++++++++++++++++------------------ 5 files changed, 57 insertions(+), 57 deletions(-) diff --git a/auth_test.go b/auth_test.go index b22d9ce..2f1ae70 100644 --- a/auth_test.go +++ b/auth_test.go @@ -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/debug_test.go b/debug_test.go index 2b01085..366d461 100644 --- a/debug_test.go +++ b/debug_test.go @@ -42,7 +42,7 @@ func TestDebugPrint(t *testing.T) { SetMode(DebugMode) debugPrint("these are %d %s\n", 2, "error messages") - assert.Equal(t, w.String(), "[GIN-debug] these are 2 error messages\n") + assert.Equal(t, "[GIN-debug] these are 2 error messages\n", w.String()) } func TestDebugPrintError(t *testing.T) { @@ -55,7 +55,7 @@ func TestDebugPrintError(t *testing.T) { assert.Empty(t, w.String()) debugPrintError(errors.New("this is an error")) - assert.Equal(t, w.String(), "[GIN-debug] [ERROR] this is an error\n") + assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", w.String()) } func TestDebugPrintRoutes(t *testing.T) { @@ -83,7 +83,7 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { defer teardown() debugPrintWARNINGSetHTMLTemplate() - assert.Equal(t, w.String(), "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n") + assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", w.String()) } func setup(w io.Writer) { diff --git a/errors_test.go b/errors_test.go index 5e596af..a666d7c 100644 --- a/errors_test.go +++ b/errors_test.go @@ -60,7 +60,7 @@ func TestError(t *testing.T) { data string } err.SetMeta(customError{status: "200", data: "other data"}) - assert.Equal(t, err.JSON(), customError{status: "200", data: "other data"}) + assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON()) } func TestErrorSlice(t *testing.T) { @@ -71,33 +71,33 @@ func TestErrorSlice(t *testing.T) { } assert.Equal(t, errs, errs.ByType(ErrorTypeAny)) - assert.Equal(t, errs.Last().Error(), "third") - assert.Equal(t, errs.Errors(), []string{"first", "second", "third"}) - assert.Equal(t, errs.ByType(ErrorTypePublic).Errors(), []string{"third"}) - assert.Equal(t, errs.ByType(ErrorTypePrivate).Errors(), []string{"first", "second"}) - assert.Equal(t, errs.ByType(ErrorTypePublic|ErrorTypePrivate).Errors(), []string{"first", "second", "third"}) + assert.Equal(t, "third", errs.Last().Error()) + assert.Equal(t, []string{"first", "second", "third"}, errs.Errors()) + assert.Equal(t, []string{"third"}, errs.ByType(ErrorTypePublic).Errors()) + assert.Equal(t, []string{"first", "second"}, errs.ByType(ErrorTypePrivate).Errors()) + assert.Equal(t, []string{"first", "second", "third"}, errs.ByType(ErrorTypePublic|ErrorTypePrivate).Errors()) assert.Empty(t, errs.ByType(ErrorTypeBind)) assert.Empty(t, errs.ByType(ErrorTypeBind).String()) - assert.Equal(t, errs.String(), `Error #01: first + assert.Equal(t, `Error #01: first Error #02: second Meta: some data Error #03: third Meta: map[status:400] -`) - assert.Equal(t, errs.JSON(), []interface{}{ +`, errs.String()) + assert.Equal(t, []interface{}{ H{"error": "first"}, H{"error": "second", "meta": "some data"}, H{"error": "third", "status": "400"}, - }) + }, errs.JSON()) jsonBytes, _ := json.Marshal(errs) assert.Equal(t, "[{\"error\":\"first\"},{\"error\":\"second\",\"meta\":\"some data\"},{\"error\":\"third\",\"status\":\"400\"}]", string(jsonBytes)) errs = errorMsgs{ {Err: errors.New("first"), Type: ErrorTypePrivate}, } - assert.Equal(t, errs.JSON(), H{"error": "first"}) + assert.Equal(t, H{"error": "first"}, errs.JSON()) jsonBytes, _ = json.Marshal(errs) - assert.Equal(t, string(jsonBytes), "{\"error\":\"first\"}") + assert.Equal(t, "{\"error\":\"first\"}", string(jsonBytes)) errs = errorMsgs{} assert.Nil(t, errs.Last()) diff --git a/middleware_test.go b/middleware_test.go index 5572e79..aa6a37a 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -37,8 +37,8 @@ func TestMiddlewareGeneralCase(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, w.Code, 200) - assert.Equal(t, signature, "ACDB") + assert.Equal(t, 200, w.Code) + assert.Equal(t, "ACDB", signature) } func TestMiddlewareNoRoute(t *testing.T) { @@ -73,8 +73,8 @@ func TestMiddlewareNoRoute(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, w.Code, 404) - assert.Equal(t, signature, "ACEGHFDB") + assert.Equal(t, 404, w.Code) + assert.Equal(t, "ACEGHFDB", signature) } func TestMiddlewareNoMethodEnabled(t *testing.T) { @@ -110,8 +110,8 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, w.Code, 405) - assert.Equal(t, signature, "ACEGHFDB") + assert.Equal(t, 405, w.Code) + assert.Equal(t, "ACEGHFDB", signature) } func TestMiddlewareNoMethodDisabled(t *testing.T) { @@ -147,8 +147,8 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, w.Code, 404) - assert.Equal(t, signature, "AC X DB") + assert.Equal(t, 404, w.Code) + assert.Equal(t, "AC X DB", signature) } func TestMiddlewareAbort(t *testing.T) { @@ -173,8 +173,8 @@ func TestMiddlewareAbort(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, w.Code, 401) - assert.Equal(t, signature, "ACD") + assert.Equal(t, 401, w.Code) + assert.Equal(t, "ACD", signature) } func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { @@ -195,8 +195,8 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, w.Code, 410) - assert.Equal(t, signature, "ACB") + assert.Equal(t, 410, w.Code) + assert.Equal(t, "ACB", signature) } // TestFailHandlersChain - ensure that Fail interrupt used middleware in fifo order as @@ -218,8 +218,8 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, w.Code, 500) - assert.Equal(t, signature, "A") + assert.Equal(t, 500, w.Code) + assert.Equal(t, "A", signature) } func TestMiddlewareWrite(t *testing.T) { diff --git a/response_writer_test.go b/response_writer_test.go index 7306d19..cec2733 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -34,11 +34,11 @@ func TestResponseWriterReset(t *testing.T) { var w ResponseWriter = writer writer.reset(testWritter) - assert.Equal(t, writer.size, -1) - assert.Equal(t, writer.status, 200) - assert.Equal(t, writer.ResponseWriter, testWritter) - assert.Equal(t, w.Size(), -1) - assert.Equal(t, w.Status(), 200) + assert.Equal(t, -1, writer.size) + assert.Equal(t, 200, writer.status) + assert.Equal(t, testWritter, writer.ResponseWriter) + assert.Equal(t, -1, w.Size()) + assert.Equal(t, 200, w.Status()) assert.False(t, w.Written()) } @@ -50,11 +50,11 @@ func TestResponseWriterWriteHeader(t *testing.T) { w.WriteHeader(300) assert.False(t, w.Written()) - assert.Equal(t, w.Status(), 300) + assert.Equal(t, 300, w.Status()) assert.NotEqual(t, testWritter.Code, 300) w.WriteHeader(-1) - assert.Equal(t, w.Status(), 300) + assert.Equal(t, 300, w.Status()) } func TestResponseWriterWriteHeadersNow(t *testing.T) { @@ -67,12 +67,12 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) { w.WriteHeaderNow() assert.True(t, w.Written()) - assert.Equal(t, w.Size(), 0) - assert.Equal(t, testWritter.Code, 300) + assert.Equal(t, 0, w.Size()) + assert.Equal(t, 300, testWritter.Code) writer.size = 10 w.WriteHeaderNow() - assert.Equal(t, w.Size(), 10) + assert.Equal(t, 10, w.Size()) } func TestResponseWriterWrite(t *testing.T) { @@ -82,17 +82,17 @@ func TestResponseWriterWrite(t *testing.T) { w := ResponseWriter(writer) n, err := w.Write([]byte("hola")) - assert.Equal(t, n, 4) - assert.Equal(t, w.Size(), 4) - assert.Equal(t, w.Status(), 200) - assert.Equal(t, testWritter.Code, 200) - assert.Equal(t, testWritter.Body.String(), "hola") + assert.Equal(t, 4, n) + assert.Equal(t, 4, w.Size()) + assert.Equal(t, 200, w.Status()) + assert.Equal(t, 200, testWritter.Code) + assert.Equal(t, "hola", testWritter.Body.String()) assert.NoError(t, err) n, err = w.Write([]byte(" adios")) - assert.Equal(t, n, 6) - assert.Equal(t, w.Size(), 10) - assert.Equal(t, testWritter.Body.String(), "hola adios") + assert.Equal(t, 6, n) + assert.Equal(t, 10, w.Size()) + assert.Equal(t, "hola adios", testWritter.Body.String()) assert.NoError(t, err) } From 4b5ec517daa247f8f3bd0448f82ca55c0d14fa0a Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Mon, 14 Aug 2017 11:02:31 +0800 Subject: [PATCH 083/912] fix(test): only check Location header (#1064) * fix(test): only check Location header * fix(test): rename field * fix(test): not export field --- routes_test.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/routes_test.go b/routes_test.go index 41693ee..b44b643 100644 --- a/routes_test.go +++ b/routes_test.go @@ -356,25 +356,25 @@ func TestRouterNotFound(t *testing.T) { router.GET("/", func(c *Context) {}) testRoutes := []struct { - route string - code int - header string + route string + code int + location string }{ - {"/path/", 301, "map[Location:[/path]]"}, // TSR -/ - {"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/ - {"", 301, "map[Location:[/]]"}, // TSR +/ - {"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case - {"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case - {"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/ - {"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/ - {"/../path", 301, "map[Location:[/path]]"}, // CleanPath - {"/nope", 404, ""}, // NotFound + {"/path/", 301, "/path"}, // TSR -/ + {"/dir", 301, "/dir/"}, // TSR +/ + {"", 301, "/"}, // TSR +/ + {"/PATH", 301, "/path"}, // Fixed Case + {"/DIR/", 301, "/dir/"}, // Fixed Case + {"/PATH/", 301, "/path"}, // Fixed Case -/ + {"/DIR", 301, "/dir/"}, // Fixed Case +/ + {"/../path", 301, "/path"}, // CleanPath + {"/nope", 404, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) assert.Equal(t, w.Code, tr.code) if w.Code != 404 { - assert.Equal(t, fmt.Sprint(w.Header()), tr.header) + assert.Equal(t, fmt.Sprint(w.Header().Get("Location")), tr.location) } } From 25d20a4463a63083b85bb2d8ebb99c7f638920f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 14 Aug 2017 12:21:05 +0800 Subject: [PATCH 084/912] merge args if it have same type (#1059) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 8347ce2..e2c0105 100644 --- a/gin.go +++ b/gin.go @@ -268,7 +268,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) }() From 52c2ed34b3256727a0aeac5e57129eb788bac045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 14 Aug 2017 14:34:29 +0800 Subject: [PATCH 085/912] log format (#1060) --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index 470e4bb..7edae41 100644 --- a/logger.go +++ b/logger.go @@ -102,7 +102,7 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { path = path + "?" + raw } - fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %s %-7s %s\n%s", + fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", end.Format("2006/01/02 - 15:04:05"), statusColor, statusCode, reset, latency, From ecae34c4e186d5190ca38ac225ce03e3894d99b7 Mon Sep 17 00:00:00 2001 From: keke <19yamashita15@gmail.com> Date: Wed, 16 Aug 2017 10:50:43 +0900 Subject: [PATCH 086/912] fix 200 to http.Status (#1067) --- examples/app-engine/hello.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/app-engine/hello.go b/examples/app-engine/hello.go index da7e4ae..f569dad 100644 --- a/examples/app-engine/hello.go +++ b/examples/app-engine/hello.go @@ -13,10 +13,10 @@ func init() { // Define your handlers r.GET("/", func(c *gin.Context) { - c.String(200, "Hello World!") + c.String(http.StatusOK, "Hello World!") }) r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) // Handle all requests using net/http From a8fa424ae529397d4a0f2a1f9fda8031851a3269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 16 Aug 2017 11:55:50 +0800 Subject: [PATCH 087/912] update comment (#1057) --- auth.go | 8 ++++---- context.go | 29 ++++++++++++++--------------- errors.go | 6 +++--- fs.go | 4 ++-- gin.go | 4 ++-- logger.go | 10 +++++----- path.go | 4 ++-- recovery.go | 2 +- render/data.go | 2 +- response_writer.go | 6 +++--- routergroup.go | 18 +++++++++--------- tree.go | 2 +- utils.go | 2 +- 13 files changed, 48 insertions(+), 49 deletions(-) diff --git a/auth.go b/auth.go index c214e21..e7c46bf 100644 --- a/auth.go +++ b/auth.go @@ -10,10 +10,10 @@ 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 { @@ -56,7 +56,7 @@ 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) } } @@ -90,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/context.go b/context.go index 895ba7a..497cbfd 100644 --- a/context.go +++ b/context.go @@ -22,7 +22,7 @@ import ( "github.com/gin-gonic/gin/render" ) -// Content-Type MIME of the most common data formats +// Content-Type MIME of the most common data formats. const ( MIMEJSON = binding.MIMEJSON MIMEHTML = binding.MIMEHTML @@ -51,13 +51,13 @@ type Context struct { engine *Engine - // Keys is a key/value pair exclusively for the context of each request + // Keys is a key/value pair exclusively for the context of each request. Keys map[string]interface{} - // Errors is a list of errors attached to all the handlers/middlewares who used this context + // Errors is a list of errors attached to all the handlers/middlewares who used this context. Errors errorMsgs - // Accepted defines a list of manually accepted formats for content negotiation + // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string } @@ -87,7 +87,7 @@ func (c *Context) Copy() *Context { } // HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", -// this function will return "main.handleGetUsers" +// this function will return "main.handleGetUsers". func (c *Context) HandlerName() string { return nameOfFunction(c.handlers.Last()) } @@ -462,18 +462,18 @@ func (c *Context) Bind(obj interface{}) error { return c.MustBindWith(obj, b) } -// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON) +// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON). func (c *Context) BindJSON(obj interface{}) error { return c.MustBindWith(obj, binding.JSON) } -// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query) +// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). func (c *Context) BindQuery(obj interface{}) error { return c.MustBindWith(obj, binding.Query) } -// MustBindWith binds the passed struct pointer using the specified binding -// engine. It will abort the request with HTTP 400 if any error ocurrs. +// MustBindWith binds the passed struct pointer using the specified binding engine. +// It will abort the request with HTTP 400 if any error ocurrs. // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { if err = c.ShouldBindWith(obj, b); err != nil { @@ -483,8 +483,7 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { return } -// ShouldBindWith binds the passed struct pointer using the specified binding -// engine. +// ShouldBindWith binds the passed struct pointer using the specified binding engine. // See the binding package. func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { return b.Bind(c.Request, obj) @@ -548,7 +547,7 @@ func (c *Context) requestHeader(key string) string { /******** RESPONSE RENDERING ********/ /************************************/ -// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function +// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function. func bodyAllowedForStatus(status int) bool { switch { case status >= 100 && status <= 199: @@ -566,7 +565,7 @@ func (c *Context) Status(code int) { c.writermem.WriteHeader(code) } -// Header is a intelligent shortcut for c.Writer.Header().Set(key, value) +// Header is a intelligent shortcut for c.Writer.Header().Set(key, value). // It writes a header in the response. // If value == "", this method removes the header `c.Writer.Header().Del(key)` func (c *Context) Header(key, value string) { @@ -577,12 +576,12 @@ func (c *Context) Header(key, value string) { } } -// GetHeader returns value from request headers +// GetHeader returns value from request headers. func (c *Context) GetHeader(key string) string { return c.requestHeader(key) } -// GetRawData return stream data +// GetRawData return stream data. func (c *Context) GetRawData() ([]byte, error) { return ioutil.ReadAll(c.Request.Body) } diff --git a/errors.go b/errors.go index 1976110..6f3c986 100644 --- a/errors.go +++ b/errors.go @@ -65,7 +65,7 @@ func (msg *Error) JSON() interface{} { return json } -// MarshalJSON implements the json.Marshaller interface +// MarshalJSON implements the json.Marshaller interface. func (msg *Error) MarshalJSON() ([]byte, error) { return json.Marshal(msg.JSON()) } @@ -80,7 +80,7 @@ func (msg *Error) IsType(flags ErrorType) bool { } // ByType returns a readonly copy filtered the byte. -// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic +// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic. func (a errorMsgs) ByType(typ ErrorType) errorMsgs { if len(a) == 0 { return nil @@ -98,7 +98,7 @@ func (a errorMsgs) ByType(typ ErrorType) errorMsgs { } // Last returns the last error in the slice. It returns nil if the array is empty. -// Shortcut for errors[len(errors)-1] +// Shortcut for errors[len(errors)-1]. func (a errorMsgs) Last() *Error { if length := len(a); length > 0 { return a[length-1] diff --git a/fs.go b/fs.go index 8570a9a..7a6738a 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 e2c0105..23853a9 100644 --- a/gin.go +++ b/gin.go @@ -14,7 +14,7 @@ import ( "github.com/gin-gonic/gin/render" ) -// Version is Framework's version +// Version is Framework's version. const Version = "v1.2" var default404Body = []byte("404 page not found") @@ -191,7 +191,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() diff --git a/logger.go b/logger.go index 7edae41..a6f7f14 100644 --- a/logger.go +++ b/logger.go @@ -25,17 +25,17 @@ var ( disableColor = false ) -// DisableConsoleColor disables color output in the console +// DisableConsoleColor disables color output in the console. func DisableConsoleColor() { disableColor = true } -// ErrorLogger returns a handlerfunc for any error type +// ErrorLogger returns a handlerfunc for any error type. func ErrorLogger() HandlerFunc { return ErrorLoggerT(ErrorTypeAny) } -// ErrorLoggerT returns a handlerfunc for a given error type +// ErrorLoggerT returns a handlerfunc for a given error type. func ErrorLoggerT(typ ErrorType) HandlerFunc { return func(c *Context) { c.Next() @@ -46,8 +46,8 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc { } } -// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter -// By default gin.DefaultWriter = os.Stdout +// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. +// By default gin.DefaultWriter = os.Stdout. func Logger() HandlerFunc { return LoggerWithWriter(DefaultWriter) } diff --git a/path.go b/path.go index e3424b1..ed63ad1 100644 --- a/path.go +++ b/path.go @@ -17,7 +17,7 @@ package gin // 4. Eliminate .. elements that begin a rooted path: // that is, replace "/.." by "/" at the beginning of a path. // -// If the result of this process is an empty string, "/" is returned +// If the result of this process is an empty string, "/" is returned. func cleanPath(p string) string { // Turn empty string into "/" if p == "" { @@ -109,7 +109,7 @@ func cleanPath(p string) string { return string(buf[:w]) } -// internal helper to lazily create a buffer if necessary +// internal helper to lazily create a buffer if necessary. func bufApp(buf *[]byte, s string, w int, c byte) { if *buf == nil { if s[w] == c { diff --git a/recovery.go b/recovery.go index c502f35..7aff3d8 100644 --- a/recovery.go +++ b/recovery.go @@ -46,7 +46,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { } } -// stack returns a nicely formated stack frame, skipping skip frames +// stack returns a nicely formated stack frame, skipping skip frames. func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data // As we loop, we open files and read them. These variables record the currently diff --git a/render/data.go b/render/data.go index c296042..3319491 100644 --- a/render/data.go +++ b/render/data.go @@ -11,7 +11,7 @@ type Data struct { Data []byte } -// Render (Data) writes data with custom ContentType +// Render (Data) writes data with custom ContentType. func (r Data) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) _, err = w.Write(r.Data) diff --git a/response_writer.go b/response_writer.go index 216165b..232f00a 100644 --- a/response_writer.go +++ b/response_writer.go @@ -95,7 +95,7 @@ func (w *responseWriter) Written() bool { return w.size != noWritten } -// Hijack implements the http.Hijacker interface +// Hijack implements the http.Hijacker interface. func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if w.size < 0 { w.size = 0 @@ -103,12 +103,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { return w.ResponseWriter.(http.Hijacker).Hijack() } -// CloseNotify implements the http.CloseNotify interface +// CloseNotify implements the http.CloseNotify interface. func (w *responseWriter) CloseNotify() <-chan bool { return w.ResponseWriter.(http.CloseNotifier).CloseNotify() } -// Flush implements the http.Flush interface +// Flush implements the http.Flush interface. func (w *responseWriter) Flush() { w.ResponseWriter.(http.Flusher).Flush() } diff --git a/routergroup.go b/routergroup.go index 89ec89a..5e681c1 100644 --- a/routergroup.go +++ b/routergroup.go @@ -35,7 +35,7 @@ type IRoutes interface { } // RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix -// and an array of handlers (middleware) +// and an array of handlers (middleware). type RouterGroup struct { Handlers HandlersChain basePath string @@ -89,43 +89,43 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha return group.handle(httpMethod, relativePath, handlers) } -// POST is a shortcut for router.Handle("POST", path, handle) +// POST is a shortcut for router.Handle("POST", path, handle). func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("POST", relativePath, handlers) } -// GET is a shortcut for router.Handle("GET", path, handle) +// GET is a shortcut for router.Handle("GET", path, handle). func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("GET", relativePath, handlers) } -// DELETE is a shortcut for router.Handle("DELETE", path, handle) +// DELETE is a shortcut for router.Handle("DELETE", path, handle). func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("DELETE", relativePath, handlers) } -// PATCH is a shortcut for router.Handle("PATCH", path, handle) +// PATCH is a shortcut for router.Handle("PATCH", path, handle). func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("PATCH", relativePath, handlers) } -// PUT is a shortcut for router.Handle("PUT", path, handle) +// PUT is a shortcut for router.Handle("PUT", path, handle). func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("PUT", relativePath, handlers) } -// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) +// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle). func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("OPTIONS", relativePath, handlers) } -// HEAD is a shortcut for router.Handle("HEAD", path, handle) +// HEAD is a shortcut for router.Handle("HEAD", path, handle). func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("HEAD", relativePath, handlers) } // Any registers a route that matches all the HTTP methods. -// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE +// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { group.handle("GET", relativePath, handlers) group.handle("POST", relativePath, handlers) diff --git a/tree.go b/tree.go index 750ffae..f67edd5 100644 --- a/tree.go +++ b/tree.go @@ -96,7 +96,7 @@ type node struct { priority uint32 } -// increments priority of the given child and reorders if necessary +// increments priority of the given child and reorders if necessary. func (n *node) incrementChildPrio(pos int) int { n.children[pos].priority++ prio := n.children[pos].priority diff --git a/utils.go b/utils.go index 968570c..ab06a75 100644 --- a/utils.go +++ b/utils.go @@ -47,7 +47,7 @@ func WrapH(h http.Handler) HandlerFunc { type H map[string]interface{} -// MarshalXML allows type H to be used with xml.Marshal +// MarshalXML allows type H to be used with xml.Marshal. func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Name = xml.Name{ Space: "", From 3856206bd07d1a5627eb951e345fff25f9b6209c Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Thu, 17 Aug 2017 12:18:50 +0800 Subject: [PATCH 088/912] doc(readme): add additional middleware doc. (#1056) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b14c2a2..dc3c866 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,7 @@ r := gin.New() instead of ```go +// Default With the Logger and Recovery middleware already attached r := gin.Default() ``` @@ -380,7 +381,10 @@ func main() { r := gin.New() // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even 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. From f4c9ac17a49e90c14ffe08b9e50aa4ee6abf6490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 22 Aug 2017 10:27:28 +0800 Subject: [PATCH 089/912] not display color when set disableColor (#1072) --- logger.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/logger.go b/logger.go index a6f7f14..c679c78 100644 --- a/logger.go +++ b/logger.go @@ -91,10 +91,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { clientIP := c.ClientIP() method := c.Request.Method statusCode := c.Writer.Status() - var statusColor, methodColor string + var statusColor, methodColor, resetColor string if isTerm { statusColor = colorForStatus(statusCode) methodColor = colorForMethod(method) + resetColor = reset } comment := c.Errors.ByType(ErrorTypePrivate).String() @@ -104,10 +105,10 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", end.Format("2006/01/02 - 15:04:05"), - statusColor, statusCode, reset, + statusColor, statusCode, resetColor, latency, clientIP, - methodColor, method, reset, + methodColor, method, resetColor, path, comment, ) From 8be30bd382890156e7e813beec9f18b2c65522a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 22 Aug 2017 13:55:32 +0800 Subject: [PATCH 090/912] Update readme for showing output log to file (#1073) * Update readme for showing output log to file * update indent --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc3c866..776ae8b 100644 --- a/README.md +++ b/README.md @@ -381,7 +381,8 @@ func main() { r := gin.New() // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release. By default gin.DefaultWriter = os.Stdout + // Logger middleware will write the logs to gin.DefaultWriter even 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. @@ -412,6 +413,27 @@ func main() { } ``` +### Output log to file +```go +func main() { + // Disable Console Color, because not need color when output log to file + gin.DisableConsoleColor() + // Create one file to save logs + f, _ := os.Create("gin.log") + // Reset gin.DefaultWriter + gin.DefaultWriter = io.MultiWriter(f) + // If need to output log to file and console at a time, please use the following code: + // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) + + router := gin.Default() + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + r.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). From 80152ac82c413e82275e828fdde8892c458715f7 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Thu, 24 Aug 2017 10:50:31 +0800 Subject: [PATCH 091/912] doc(readme): update writing logs section wording. (#1074) * doc(readme): update writing logs section wording. * doc(readme): update writing logs section wording. * doc(readme): fix word formatting --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 776ae8b..b564446 100644 --- a/README.md +++ b/README.md @@ -413,16 +413,17 @@ func main() { } ``` -### Output log to file +### How to write log file ```go func main() { - // Disable Console Color, because not need color when output log to file + // Disable Console Color, you don't need console color when writing the logs to file. gin.DisableConsoleColor() - // Create one file to save logs + + // Logging to a file. f, _ := os.Create("gin.log") - // Reset gin.DefaultWriter gin.DefaultWriter = io.MultiWriter(f) - // If need to output log to file and console at a time, please use the following code: + + // 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() From c25254f563fc3685e22901edf2fd289902a76cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 25 Aug 2017 09:00:49 +0800 Subject: [PATCH 092/912] reduce go cyclo (#1076) * reduce go cyclo * use := var --- gin.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gin.go b/gin.go index 23853a9..ee39c9a 100644 --- a/gin.go +++ b/gin.go @@ -316,14 +316,11 @@ func (engine *Engine) HandleContext(c *Context) { func (engine *Engine) handleHTTPRequest(context *Context) { httpMethod := context.Request.Method - var path string - var unescape bool + path := context.Request.URL.Path + unescape := false if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 { path = context.Request.URL.RawPath unescape = engine.UnescapePathValues - } else { - path = context.Request.URL.Path - unescape = false } // Find root of the tree for the given HTTP method From bc538849ebc41f2600338d9f65fad2c3008c95b2 Mon Sep 17 00:00:00 2001 From: stackerzzq Date: Fri, 25 Aug 2017 09:06:13 +0800 Subject: [PATCH 093/912] Update readme for adding tag example of binding time field (#1080) --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b564446..879d77b 100644 --- a/README.md +++ b/README.md @@ -532,10 +532,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() { @@ -552,12 +554,18 @@ func startPage(c *gin.Context) { if c.Bind(&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) From 18b7c0892df51ba799473e57fd3d65f91c1c3aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 25 Aug 2017 09:13:53 +0800 Subject: [PATCH 094/912] not use dot when import package (#1077) --- ginS/gins.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index d40d1c3..d238fca 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 } From b9686e91fa271d2fe5da3108a2ea4087c0b0ad68 Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Fri, 25 Aug 2017 21:53:27 -0700 Subject: [PATCH 095/912] Use standard library for retrieving header (#1081) --- context.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/context.go b/context.go index 497cbfd..6f2b005 100644 --- a/context.go +++ b/context.go @@ -537,10 +537,7 @@ func (c *Context) IsWebsocket() bool { } func (c *Context) requestHeader(key string) string { - if values, _ := c.Request.Header[key]; len(values) > 0 { - return values[0] - } - return "" + return c.Request.Header.Get(key) } /************************************/ From fa391a4864cb2e249b4e82fd1d71261a1496f0f4 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 26 Aug 2017 03:48:56 -0500 Subject: [PATCH 096/912] chore(ci): add go 1.9 version (#1082) Signed-off-by: Bo-Yi Wu --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 821ce8d..ec12cad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ go: - 1.6.x - 1.7.x - 1.8.x + - 1.9.x - master git: From 211c48f0408804fdbc3b698cf53243da9699c376 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 26 Aug 2017 05:02:47 -0500 Subject: [PATCH 097/912] refactor: using requestHeader internal func (#1083) * refactor: using requestHeader internal func. * update Signed-off-by: Bo-Yi Wu --- auth.go | 2 +- context.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/auth.go b/auth.go index e7c46bf..85d96fd 100644 --- a/auth.go +++ b/auth.go @@ -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) diff --git a/context.go b/context.go index 6f2b005..2be7010 100644 --- a/context.go +++ b/context.go @@ -509,7 +509,7 @@ func (c *Context) ClientIP() string { } if c.engine.AppEngine { - if addr := c.Request.Header.Get("X-Appengine-Remote-Addr"); addr != "" { + if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { return addr } } From 030b1aaf72ce020aa38b6c4bbdd6a4baf746051d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 26 Aug 2017 12:37:19 -0500 Subject: [PATCH 098/912] chore(ci): replace travis ci go 1.9.x for 1.9 (#1085) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ec12cad..72daa3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ go: - 1.6.x - 1.7.x - 1.8.x - - 1.9.x + - 1.9 - master git: From 26c3f42095b8a1378617eb4756fbf8282cb1ae89 Mon Sep 17 00:00:00 2001 From: Suhas Karanth Date: Sun, 27 Aug 2017 13:07:39 +0530 Subject: [PATCH 099/912] feat(binding): add support for custom validator / validation tags (#1068) * feat(binding): Add support for custom validation tags * docs: Add example for custom validation tag * test(binding): Add test for registering custom validation --- README.md | 85 ++++++++++++++++++++++++---- binding/binding.go | 11 +++- binding/default_validator.go | 5 ++ binding/validate_test.go | 42 ++++++++++++++ examples/custom-validation/server.go | 45 +++++++++++++++ 5 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 examples/custom-validation/server.go diff --git a/README.md b/README.md index 879d77b..07357be 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ $ 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) @@ -74,10 +74,10 @@ BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848 BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609 -(1): Total Repetitions achieved in constant time, higher means more confident result -(2): Single Repetition Duration (ns/op), lower is better -(3): Heap Memory (B/op), lower is better -(4): Average Allocations per Repetition (allocs/op), lower is better +(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 @@ -281,10 +281,10 @@ func main() { // 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") @@ -313,9 +313,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))) }) @@ -487,6 +487,67 @@ func main() { } ``` +### 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" + validator "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() + binding.Validator.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=2017-08-16&check_out=2017-08-17" +{"message":"Booking dates are valid!"} + +$ curl "localhost:8085/bookable?check_in=2017-08-15&check_out=2017-08-16" +{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} +``` + ### Only Bind Query String `BindQuery` 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). @@ -711,7 +772,7 @@ func main() { // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } -``` +``` ### Serving static files @@ -822,7 +883,7 @@ You may use custom delims r := gin.Default() r.Delims("{[{", "}]}") r.LoadHTMLGlob("/path/to/templates")) -``` +``` #### Custom Template Funcs diff --git a/binding/binding.go b/binding/binding.go index 971547c..a09cc22 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -4,7 +4,11 @@ package binding -import "net/http" +import ( + "net/http" + + validator "gopkg.in/go-playground/validator.v8" +) const ( MIMEJSON = "application/json" @@ -31,6 +35,11 @@ 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 + + // RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key + // NOTE: if the key already exists, the previous validation function will be replaced. + // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation + RegisterValidation(string, validator.Func) error } var Validator StructValidator = &defaultValidator{} diff --git a/binding/default_validator.go b/binding/default_validator.go index 19885f1..6336bb6 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -28,6 +28,11 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error { return nil } +func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error { + v.lazyinit() + return v.validate.RegisterValidation(key, fn) +} + func (v *defaultValidator) lazyinit() { v.once.Do(func() { config := &validator.Config{TagName: "binding"} diff --git a/binding/validate_test.go b/binding/validate_test.go index cbcb389..523e129 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -6,9 +6,12 @@ package binding import ( "bytes" + "reflect" "testing" "time" + validator "gopkg.in/go-playground/validator.v8" + "github.com/stretchr/testify/assert" ) @@ -190,3 +193,42 @@ func TestValidatePrimitives(t *testing.T) { assert.NoError(t, validate(&str)) assert.Equal(t, str, "value") } + +// structCustomValidation is a helper struct we use to check that +// custom validation can be registered on it. +// The `notone` binding directive is for custom validation and registered later. +type structCustomValidation struct { + Integer int `binding:"notone"` +} + +// notOne is a custom validator meant to be used with `validator.v8` library. +// The method signature for `v9` is significantly different and this function +// would need to be changed for tests to pass after upgrade. +// See https://github.com/gin-gonic/gin/pull/1015. +func notOne( + v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, + field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, +) bool { + if val, ok := field.Interface().(int); ok { + return val != 1 + } + return false +} + +func TestRegisterValidation(t *testing.T) { + // This validates that the function `notOne` matches + // the expected function signature by `defaultValidator` + // and by extension the validator library. + err := Validator.RegisterValidation("notone", notOne) + // Check that we can register custom validation without error + assert.Nil(t, err) + + // Create an instance which will fail validation + withOne := structCustomValidation{Integer: 1} + errs := validate(withOne) + + // Check that we got back non-nil errs + assert.NotNil(t, errs) + // Check that the error matches expactation + assert.Error(t, errs, "", "", "notone") +} diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go new file mode 100644 index 0000000..0b67ce1 --- /dev/null +++ b/examples/custom-validation/server.go @@ -0,0 +1,45 @@ +package main + +import ( + "net/http" + "reflect" + "time" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + validator "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() + binding.Validator.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()}) + } +} From f1edd2c2d1c0ff06ab0a3e8f3c6a3d58ee4a0a68 Mon Sep 17 00:00:00 2001 From: vz Date: Sun, 27 Aug 2017 17:11:38 +0800 Subject: [PATCH 100/912] fix(ginS): fix undefined ref (#1087) --- ginS/gins.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index d238fca..ee00b38 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -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...) } From 8902826696c1dad024e1c8ba4f903aa61a25c839 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 29 Aug 2017 03:38:53 +0800 Subject: [PATCH 101/912] doc(context): add cookie doc (#1088) --- context.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/context.go b/context.go index 2be7010..d7b6c61 100644 --- a/context.go +++ b/context.go @@ -583,6 +583,9 @@ func (c *Context) GetRawData() ([]byte, error) { return ioutil.ReadAll(c.Request.Body) } +// SetCookie adds a Set-Cookie header to the ResponseWriter's headers. +// The provided cookie must have a valid Name. Invalid cookies may be +// silently dropped. func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { if path == "" { path = "/" @@ -598,6 +601,10 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, }) } +// Cookie returns the named cookie provided in the request or +// ErrNoCookie if not found. And return the named cookie is unescaped. +// If multiple cookies match the given name, only one cookie will +// be returned. func (c *Context) Cookie(name string) (string, error) { cookie, err := c.Request.Cookie(name) if err != nil { From c16c2b7ec33175b23667be512a74dbb7f3943aa5 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Wed, 30 Aug 2017 12:18:57 +0800 Subject: [PATCH 102/912] chore(vendor): update jsoniter rev, #1086 (#1090) --- vendor/vendor.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index e8690a2..bcefee1 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -34,10 +34,10 @@ "revisionTime": "2017-06-01T23:02:30Z" }, { - "checksumSHA1": "0e59uuETpidkmpaRwipQ8auqwhM=", + "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "path": "github.com/json-iterator/go", - "revision": "6b6938829d6156d7b9825f83eec757f0f571c981", - "revisionTime": "2017-07-18T14:19:52Z" + "revision": "36b14963da70d11297d313183d7e6388c8510e1e", + "revisionTime": "2017-08-29T15:58:51Z" }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", From ab50cf97900b63748a62a81c06c9189ef536d121 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Fri, 1 Sep 2017 15:02:25 +0100 Subject: [PATCH 103/912] correct spelling mistake (#1092) --- recovery.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 7aff3d8..dfea8c2 100644 --- a/recovery.go +++ b/recovery.go @@ -46,7 +46,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { } } -// stack returns a nicely formated stack frame, skipping skip frames. +// stack returns a nicely formatted stack frame, skipping skip frames. func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data // As we loop, we open files and read them. These variables record the currently From cdf26f994bb551da109f4e4b8f206d8db4fdb354 Mon Sep 17 00:00:00 2001 From: George Kirilenko Date: Mon, 4 Sep 2017 04:15:50 +0300 Subject: [PATCH 104/912] 32 << 10 != 32 Mb (#1094) --- binding/form.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/binding/form.go b/binding/form.go index 557333e..0be5966 100644 --- a/binding/form.go +++ b/binding/form.go @@ -6,6 +6,8 @@ package binding import "net/http" +const defaultMemory = 32 * 1024 * 1024 + type formBinding struct{} type formPostBinding struct{} type formMultipartBinding struct{} @@ -18,7 +20,7 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } - req.ParseMultipartForm(32 << 10) // 32 MB + req.ParseMultipartForm(defaultMemory) if err := mapForm(obj, req.Form); err != nil { return err } @@ -44,7 +46,7 @@ func (formMultipartBinding) Name() string { } func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { - if err := req.ParseMultipartForm(32 << 10); err != nil { + if err := req.ParseMultipartForm(defaultMemory); err != nil { return err } if err := mapForm(obj, req.MultipartForm.Value); err != nil { From 848fa41ca016fa3a3d385af710c4219c1cb477a4 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 5 Sep 2017 09:17:53 +0800 Subject: [PATCH 105/912] doc(recovery): add RecoveryWithWriter doc (#1097) --- recovery.go | 1 + 1 file changed, 1 insertion(+) diff --git a/recovery.go b/recovery.go index dfea8c2..89b39fe 100644 --- a/recovery.go +++ b/recovery.go @@ -26,6 +26,7 @@ func Recovery() HandlerFunc { return RecoveryWithWriter(DefaultErrorWriter) } +// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one. func RecoveryWithWriter(out io.Writer) HandlerFunc { var logger *log.Logger if out != nil { From c9b344118f6426e9211c2e4530ac983a541f437a Mon Sep 17 00:00:00 2001 From: "Daniel M. Lambea" Date: Thu, 7 Sep 2017 04:45:16 +0100 Subject: [PATCH 106/912] Moved const 'defaultMemory' to attrib. Engine.MaxMultipartMemory instead. (#1100) --- README.md | 4 ++++ context.go | 7 +++---- examples/upload-file/multiple/main.go | 2 ++ examples/upload-file/single/main.go | 2 ++ gin.go | 10 +++++++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 07357be..d16a868 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,8 @@ 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") @@ -306,6 +308,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() diff --git a/context.go b/context.go index d7b6c61..74995e6 100644 --- a/context.go +++ b/context.go @@ -34,8 +34,7 @@ const ( ) const ( - defaultMemory = 32 << 20 // 32 MB - abortIndex int8 = math.MaxInt8 / 2 + abortIndex int8 = math.MaxInt8 / 2 ) // Context is the most important part of gin. It allows us to pass variables between middleware, @@ -407,7 +406,7 @@ func (c *Context) PostFormArray(key string) []string { func (c *Context) GetPostFormArray(key string) ([]string, bool) { req := c.Request req.ParseForm() - req.ParseMultipartForm(defaultMemory) + req.ParseMultipartForm(c.engine.MaxMultipartMemory) if values := req.PostForm[key]; len(values) > 0 { return values, true } @@ -427,7 +426,7 @@ func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { // MultipartForm is the parsed multipart form, including file uploads. func (c *Context) MultipartForm() (*multipart.Form, error) { - err := c.Request.ParseMultipartForm(defaultMemory) + err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory) return c.Request.MultipartForm, err } diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go index 4bb4cdc..a55325e 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 372a299..5d43865 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/gin.go b/gin.go index ee39c9a..24c4dd8 100644 --- a/gin.go +++ b/gin.go @@ -15,7 +15,10 @@ import ( ) // Version is Framework's version. -const Version = "v1.2" +const ( + Version = "v1.2" + defaultMultipartMemory = 32 << 20 // 32 MB +) var default404Body = []byte("404 page not found") var default405Body = []byte("405 method not allowed") @@ -92,6 +95,10 @@ type Engine struct { // 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 } var _ IRouter = &Engine{} @@ -120,6 +127,7 @@ func New() *Engine { AppEngine: defaultAppEngine, UseRawPath: false, UnescapePathValues: true, + MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", From b1ee49de8c80ea72acf019315d3dffc0ce6e06ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 8 Sep 2017 08:56:56 +0800 Subject: [PATCH 107/912] fix typo (#1103) --- deprecated.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deprecated.go b/deprecated.go index 7b50dc7..ab44742 100644 --- a/deprecated.go +++ b/deprecated.go @@ -15,7 +15,7 @@ import ( func (c *Context) BindWith(obj interface{}, b binding.Binding) error { log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to be deprecated, please check issue #662 and either use MustBindWith() if you - want HTTP 400 to be automatically returned if any error occur, of use + want HTTP 400 to be automatically returned if any error occur, or use ShouldBindWith() if you need to manage the error.`) return c.MustBindWith(obj, b) } From 8c17c680d9e4a6a6d7c2fd48eafe463eac23372e Mon Sep 17 00:00:00 2001 From: "Kristoffer A. Iversen" Date: Mon, 11 Sep 2017 16:17:26 +0200 Subject: [PATCH 108/912] Fixed README.md typo (#1104) * Fixed README.md typo * Fixed example typo * Fixed example typo --- README.md | 2 +- examples/graceful-shutdown/close/server.go | 2 +- examples/graceful-shutdown/graceful-shutdown/server.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d16a868..0dc165b 100644 --- a/README.md +++ b/README.md @@ -1224,7 +1224,7 @@ 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/graceful-shutdown/close/server.go b/examples/graceful-shutdown/close/server.go index 5477839..9c4e90f 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 060de08..6debe7f 100644 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -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") } From 5afc5b19730118c9b8324fe9dd995d44ec65c81a Mon Sep 17 00:00:00 2001 From: Davor Kapsa Date: Mon, 11 Sep 2017 16:33:19 +0200 Subject: [PATCH 109/912] travis: add 1.9.x instead 1.9 to go version (#1105) 1.9.x should be working now. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 72daa3f..ec12cad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ go: - 1.6.x - 1.7.x - 1.8.x - - 1.9 + - 1.9.x - master git: From a8c53949e5b7d8a00a9e26b1609a7626e7d0b127 Mon Sep 17 00:00:00 2001 From: delphinus Date: Thu, 28 Sep 2017 23:23:18 +0900 Subject: [PATCH 110/912] Support time location on form binding (#1117) --- binding/form_mapping.go | 8 ++++++++ context_test.go | 21 +++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 34f1267..c968dc0 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -163,6 +163,14 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val l = time.UTC } + if locTag := structField.Tag.Get("time_location"); locTag != "" { + loc, err := time.LoadLocation(locTag) + if err != nil { + return err + } + l = loc + } + t, err := time.ParseInLocation(timeFormat, val, l) if err != nil { return err diff --git a/context_test.go b/context_test.go index 15569bf..4854a8a 100644 --- a/context_test.go +++ b/context_test.go @@ -45,6 +45,7 @@ func createMultipartRequest() *http.Request { must(mw.WriteField("id", "")) must(mw.WriteField("time_local", "31/12/2016 14:55")) must(mw.WriteField("time_utc", "31/12/2016 14:55")) + must(mw.WriteField("time_location", "31/12/2016 14:55")) req, err := http.NewRequest("POST", "/", body) must(err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -444,14 +445,15 @@ func TestContextPostFormMultipart(t *testing.T) { c.Request = createMultipartRequest() var obj struct { - Foo string `form:"foo"` - Bar string `form:"bar"` - BarAsInt int `form:"bar"` - Array []string `form:"array"` - ID string `form:"id"` - TimeLocal time.Time `form:"time_local" time_format:"02/01/2006 15:04"` - TimeUTC time.Time `form:"time_utc" time_format:"02/01/2006 15:04" time_utc:"1"` - BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"` + Foo string `form:"foo"` + Bar string `form:"bar"` + BarAsInt int `form:"bar"` + Array []string `form:"array"` + ID string `form:"id"` + TimeLocal time.Time `form:"time_local" time_format:"02/01/2006 15:04"` + TimeUTC time.Time `form:"time_utc" time_format:"02/01/2006 15:04" time_utc:"1"` + TimeLocation time.Time `form:"time_location" time_format:"02/01/2006 15:04" time_location:"Asia/Tokyo"` + BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"` } assert.NoError(t, c.Bind(&obj)) assert.Equal(t, obj.Foo, "bar") @@ -463,6 +465,9 @@ func TestContextPostFormMultipart(t *testing.T) { assert.Equal(t, obj.TimeLocal.Location(), time.Local) assert.Equal(t, obj.TimeUTC.Format("02/01/2006 15:04"), "31/12/2016 14:55") assert.Equal(t, obj.TimeUTC.Location(), time.UTC) + loc, _ := time.LoadLocation("Asia/Tokyo") + assert.Equal(t, obj.TimeLocation.Format("02/01/2006 15:04"), "31/12/2016 14:55") + assert.Equal(t, obj.TimeLocation.Location(), loc) assert.True(t, obj.BlankTime.IsZero()) value, ok := c.GetQuery("foo") From f7376f7c7fc73e2fdd30623f413f87a32e73baa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 28 Sep 2017 09:54:37 -0500 Subject: [PATCH 111/912] combine var and use tmp var (#1108) --- gin.go | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/gin.go b/gin.go index 24c4dd8..3a51bc1 100644 --- a/gin.go +++ b/gin.go @@ -20,9 +20,11 @@ const ( 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 @@ -91,6 +93,7 @@ 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. @@ -161,13 +164,16 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { } 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) } @@ -322,12 +328,12 @@ func (engine *Engine) HandleContext(c *Context) { engine.pool.Put(c) } -func (engine *Engine) handleHTTPRequest(context *Context) { - httpMethod := context.Request.Method - path := context.Request.URL.Path +func (engine *Engine) handleHTTPRequest(c *Context) { + httpMethod := c.Request.Method + path := c.Request.URL.Path unescape := false - if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 { - path = context.Request.URL.RawPath + if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { + path = c.Request.URL.RawPath unescape = engine.UnescapePathValues } @@ -337,20 +343,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 } } @@ -362,15 +368,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} From 3b300929e80cede939bd5f0016b6692d20491ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 28 Sep 2017 11:22:35 -0500 Subject: [PATCH 112/912] Empty string check (#1101) --- auth.go | 4 ++-- gin.go | 2 +- mode.go | 2 +- render/html.go | 4 ++-- utils.go | 11 +++++------ 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/auth.go b/auth.go index 85d96fd..302a4fa 100644 --- a/auth.go +++ b/auth.go @@ -24,7 +24,7 @@ type authPair struct { type authPairs []authPair func (a authPairs) searchCredential(authValue string) (string, bool) { - if len(authValue) == 0 { + if authValue == "" { return "", false } for _, pair := range a { @@ -71,7 +71,7 @@ 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, diff --git a/gin.go b/gin.go index 3a51bc1..9466e07 100644 --- a/gin.go +++ b/gin.go @@ -231,7 +231,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) diff --git a/mode.go b/mode.go index b0d2c27..393e13e 100644 --- a/mode.go +++ b/mode.go @@ -39,7 +39,7 @@ var modeName = DebugMode func init() { mode := os.Getenv(ENV_GIN_MODE) - if len(mode) == 0 { + if mode == "" { SetMode(DebugMode) } else { SetMode(mode) diff --git a/render/html.go b/render/html.go index 332d3ba..1e3be65 100644 --- a/render/html.go +++ b/render/html.go @@ -60,7 +60,7 @@ func (r HTMLDebug) loadTemplate() *template.Template { if len(r.Files) > 0 { return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...)) } - if len(r.Glob) > 0 { + if r.Glob != "" { return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob)) } panic("the HTML debug render was created without files or glob pattern") @@ -69,7 +69,7 @@ func (r HTMLDebug) loadTemplate() *template.Template { func (r HTML) Render(w http.ResponseWriter) error { r.WriteContentType(w) - if len(r.Name) == 0 { + if r.Name == "" { return r.Template.Execute(w, r.Data) } return r.Template.ExecuteTemplate(w, r.Name, r.Data) diff --git a/utils.go b/utils.go index ab06a75..e909bb7 100644 --- a/utils.go +++ b/utils.go @@ -103,7 +103,7 @@ func parseAccept(acceptHeader string) []string { if index := strings.IndexByte(part, ';'); index >= 0 { part = part[0:index] } - if part = strings.TrimSpace(part); len(part) > 0 { + if part = strings.TrimSpace(part); part != "" { out = append(out, part) } } @@ -111,11 +111,10 @@ func parseAccept(acceptHeader string) []string { } func lastChar(str string) uint8 { - size := len(str) - if size == 0 { + if str == "" { panic("The length of the string can't be 0") } - return str[size-1] + return str[len(str)-1] } func nameOfFunction(f interface{}) string { @@ -123,7 +122,7 @@ func nameOfFunction(f interface{}) string { } func joinPaths(absolutePath, relativePath string) string { - if len(relativePath) == 0 { + if relativePath == "" { return absolutePath } @@ -138,7 +137,7 @@ func joinPaths(absolutePath, relativePath string) string { func resolveAddress(addr []string) string { switch len(addr) { case 0: - if port := os.Getenv("PORT"); len(port) > 0 { + if port := os.Getenv("PORT"); port != "" { debugPrint("Environment variable PORT=\"%s\"", port) return ":" + port } From 0cb7c44abc8280411ee5ab3cc67ff63aaee2bf3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 28 Sep 2017 22:58:57 -0500 Subject: [PATCH 113/912] Print warning log when user create one engine using gin.Default in debug mode (#1121) * empty string check * add log when use Default * fix unit test error * fix unit test error --- debug.go | 6 ++++++ debug_test.go | 18 ++++++++++++++++++ gin.go | 1 + 3 files changed, 25 insertions(+) diff --git a/debug.go b/debug.go index b31ca68..449291e 100644 --- a/debug.go +++ b/debug.go @@ -46,6 +46,12 @@ func debugPrint(format string, values ...interface{}) { } } +func debugPrintWARNINGDefault() { + debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. + +`) +} + func debugPrintWARNINGNew() { debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release diff --git a/debug_test.go b/debug_test.go index 366d461..dfd54c8 100644 --- a/debug_test.go +++ b/debug_test.go @@ -86,6 +86,24 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", w.String()) } +func TestDebugPrintWARNINGDefault(t *testing.T) { + var w bytes.Buffer + setup(&w) + defer teardown() + + debugPrintWARNINGDefault() + assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) +} + +func TestDebugPrintWARNINGNew(t *testing.T) { + var w bytes.Buffer + setup(&w) + defer teardown() + + debugPrintWARNINGNew() + assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", w.String()) +} + func setup(w io.Writer) { SetMode(DebugMode) log.SetOutput(w) diff --git a/gin.go b/gin.go index 9466e07..8cfb1f1 100644 --- a/gin.go +++ b/gin.go @@ -144,6 +144,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 From b8b68314faa067acae5b17c828127b04af51ebe2 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 29 Sep 2017 16:48:10 +0800 Subject: [PATCH 114/912] feat: add multiple service example. (#1119) --- README.md | 82 +++++++++++++++++++++++++++++++ examples/multiple-service/main.go | 74 ++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 examples/multiple-service/main.go diff --git a/README.md b/README.md index 0dc165b..66c83fb 100644 --- a/README.md +++ b/README.md @@ -1154,6 +1154,88 @@ func main() { } ``` +### Run multiple service using Gin + +See the [question](https://github.com/gin-gonic/gin/issues/346) and try the folling 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? diff --git a/examples/multiple-service/main.go b/examples/multiple-service/main.go new file mode 100644 index 0000000..ceddaa2 --- /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) + } +} From dfb68ce0852d3fe600148f486f67d2327146dfea Mon Sep 17 00:00:00 2001 From: Suhas Karanth Date: Mon, 23 Oct 2017 14:44:09 +0530 Subject: [PATCH 115/912] feat(context): ShouldBind counterparts for Bind methods (#1047) * feat(context): ShouldBind counterparts for Bind methods + tests * docs(readme): Switch examples to use ShouldBind methods Add section for bind methods types, explain difference in behavior. Switch all `c.Bind` examples to use `c.ShouldBind`. --- README.md | 62 ++++++++++++++++++++++++++++++++++----------- context.go | 23 +++++++++++++++++ context_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 66c83fb..7113058 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ func main() { // Logger middleware will write the logs to gin.DefaultWriter even 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()) @@ -422,11 +422,11 @@ func main() { 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) @@ -447,9 +447,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. + +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, the current request will fail with an error. +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 @@ -464,12 +472,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()}) } }) @@ -477,12 +487,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()}) } }) @@ -491,6 +503,28 @@ func main() { } ``` +**Sample request** +```shell +$ curl -v -X POST \ + http://localhost:8080/loginJSON \ + -H 'content-type: application/json' \ + -d '{ "user": "manu" }' +> POST /loginJSON HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.51.0 +> Accept: */* +> content-type: application/json +> Content-Length: 18 +> +* upload completely sent off: 18 out of 18 bytes +< HTTP/1.1 400 Bad Request +< Content-Type: application/json; charset=utf-8 +< Date: Fri, 04 Aug 2017 03:51:31 GMT +< Content-Length: 100 +< +{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} +``` + ### Custom Validators It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go). @@ -554,7 +588,7 @@ $ curl "localhost:8085/bookable?check_in=2017-08-15&check_out=2017-08-16" ### Only Bind Query String -`BindQuery` 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). +`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 @@ -578,7 +612,7 @@ func main() { func startPage(c *gin.Context) { var person Person - if c.BindQuery(&person) == nil { + if c.ShouldBindQuery(&person) == nil { log.Println("====== Only Bind By Query String ======") log.Println(person.Name) log.Println(person.Address) @@ -616,7 +650,7 @@ 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) @@ -648,7 +682,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}) } @@ -695,11 +729,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 { diff --git a/context.go b/context.go index 74995e6..5b67dcc 100644 --- a/context.go +++ b/context.go @@ -482,6 +482,29 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { return } +// ShouldBind checks the Content-Type to select a binding engine automatically, +// Depending the "Content-Type" header different bindings are used: +// "application/json" --> JSON binding +// "application/xml" --> XML binding +// otherwise --> returns an error +// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. +// It decodes the json payload into the struct specified as a pointer. +// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid. +func (c *Context) ShouldBind(obj interface{}) error { + b := binding.Default(c.Request.Method, c.ContentType()) + return c.ShouldBindWith(obj, b) +} + +// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). +func (c *Context) ShouldBindJSON(obj interface{}) error { + return c.ShouldBindWith(obj, binding.JSON) +} + +// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). +func (c *Context) ShouldBindQuery(obj interface{}) error { + return c.ShouldBindWith(obj, binding.Query) +} + // ShouldBindWith binds the passed struct pointer using the specified binding engine. // See the binding package. func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { diff --git a/context_test.go b/context_test.go index 4854a8a..8cd05f6 100644 --- a/context_test.go +++ b/context_test.go @@ -1228,6 +1228,73 @@ func TestContextBadAutoBind(t *testing.T) { assert.True(t, c.IsAborted()) } +func TestContextAutoShouldBindJSON(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request.Header.Add("Content-Type", MIMEJSON) + + var obj struct { + Foo string `json:"foo"` + Bar string `json:"bar"` + } + assert.NoError(t, c.ShouldBind(&obj)) + assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, obj.Foo, "bar") + assert.Empty(t, c.Errors) +} + +func TestContextShouldBindWithJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type + + var obj struct { + Foo string `json:"foo"` + Bar string `json:"bar"` + } + assert.NoError(t, c.ShouldBindJSON(&obj)) + assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, w.Body.Len(), 0) +} + +func TestContextShouldBindWithQuery(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + + var obj struct { + Foo string `form:"foo"` + Bar string `form:"bar"` + } + assert.NoError(t, c.ShouldBindQuery(&obj)) + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, 0, w.Body.Len()) +} + +func TestContextBadAutoShouldBind(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request.Header.Add("Content-Type", MIMEJSON) + var obj struct { + Foo string `json:"foo"` + Bar string `json:"bar"` + } + + assert.False(t, c.IsAborted()) + assert.Error(t, c.ShouldBind(&obj)) + + assert.Empty(t, obj.Bar) + assert.Empty(t, obj.Foo) + assert.False(t, c.IsAborted()) +} + func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) From 1e88466d234a82ce4aeca904bce8a0b93fac3d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 26 Oct 2017 10:25:25 +0800 Subject: [PATCH 116/912] Update the comment of Version (#1141) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 8cfb1f1..4857f3a 100644 --- a/gin.go +++ b/gin.go @@ -14,8 +14,8 @@ import ( "github.com/gin-gonic/gin/render" ) -// Version is Framework's version. const ( + // Version is Framework's version. Version = "v1.2" defaultMultipartMemory = 32 << 20 // 32 MB ) From b7e8a6b9b062473c7f3f4f5c16d0a28b6244de48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 29 Oct 2017 20:12:22 +0800 Subject: [PATCH 117/912] style(import): not use aliase when import package (#1146) --- binding/binding.go | 2 +- binding/json.go | 4 +--- binding/query.go | 4 +--- binding/validate_test.go | 3 +-- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index a09cc22..dc32d53 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -7,7 +7,7 @@ package binding import ( "net/http" - validator "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v8" ) const ( diff --git a/binding/json.go b/binding/json.go index f600a5f..b7c856a 100644 --- a/binding/json.go +++ b/binding/json.go @@ -10,9 +10,7 @@ import ( "github.com/gin-gonic/gin/json" ) -var ( - EnableDecoderUseNumber = false -) +var EnableDecoderUseNumber = false type jsonBinding struct{} diff --git a/binding/query.go b/binding/query.go index a789f79..219743f 100644 --- a/binding/query.go +++ b/binding/query.go @@ -4,9 +4,7 @@ package binding -import ( - "net/http" -) +import "net/http" type queryBinding struct{} diff --git a/binding/validate_test.go b/binding/validate_test.go index 523e129..8ca7998 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -10,9 +10,8 @@ import ( "testing" "time" - validator "gopkg.in/go-playground/validator.v8" - "github.com/stretchr/testify/assert" + "gopkg.in/go-playground/validator.v8" ) type testInterface interface { From 6653d5d588740eb73d2245ef04d5a407d3c3f034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 30 Oct 2017 10:38:38 +0800 Subject: [PATCH 118/912] fix markdown render (#1149) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7113058..b718103 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,10 @@ BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848 BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609 -(1): Total Repetitions achieved in constant time, higher means more confident result -(2): Single Repetition Duration (ns/op), lower is better -(3): Heap Memory (B/op), lower is better -(4): Average Allocations per Repetition (allocs/op), lower is better +- (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 From 76ad15ab32d000115cb7622a8317d7425dfc039c Mon Sep 17 00:00:00 2001 From: Mike Stipicevic Date: Thu, 2 Nov 2017 14:48:54 +0100 Subject: [PATCH 119/912] Update comment to reflect correct constant. (#1144) The constant is `DebugMode`, as defined in [mode.go](https://github.com/gin-gonic/gin/blob/1e88466d234a82ce4aeca904bce8a0b93fac3d42/mode.go#L16). --- debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug.go b/debug.go index 449291e..897c494 100644 --- a/debug.go +++ b/debug.go @@ -15,7 +15,7 @@ func init() { } // IsDebugging returns true if the framework is running in debug mode. -// Use SetMode(gin.Release) to disable debug mode. +// Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { return ginMode == debugCode } From 9ae1e5db2a47219bfae9147d372bd07b21ca0e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Nov 2017 13:11:22 +0800 Subject: [PATCH 120/912] add package for govendor (#1166) * add package for govendor * fix vet error. Signed-off-by: Bo-Yi Wu * add missing example. Signed-off-by: Bo-Yi Wu * update gin-gonic/autotls Signed-off-by: Bo-Yi Wu --- README.md | 4 ++-- .../auto-tls/{example1.go => example1/main.go} | 0 .../auto-tls/{example2.go => example2/main.go} | 0 vendor/vendor.json | 18 +++++++++++++++--- 4 files changed, 17 insertions(+), 5 deletions(-) rename examples/auto-tls/{example1.go => example1/main.go} (100%) rename examples/auto-tls/{example2.go => example2/main.go} (100%) diff --git a/README.md b/README.md index b718103..6b8cdd7 100644 --- a/README.md +++ b/README.md @@ -1133,7 +1133,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 @@ -1158,7 +1158,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 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/vendor/vendor.json b/vendor/vendor.json index bcefee1..573abe2 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -22,10 +22,10 @@ "revisionTime": "2017-01-09T09:34:21Z" }, { - "checksumSHA1": "FJKrZuFmeLJp8HDeJc6UkIDBPUw=", + "checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=", "path": "github.com/gin-gonic/autotls", - "revision": "5b3297bdcee778ff3bbdc99ab7c41e1c2677d22d", - "revisionTime": "2017-04-16T09:39:34Z" + "revision": "8ca25fbde72bb72a00466215b94b489c71fcb815", + "revisionTime": "2017-09-16T16:54:15Z" }, { "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", @@ -65,6 +65,12 @@ "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revisionTime": "2016-09-25T22:06:09Z" }, + { + "checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=", + "path": "github.com/thinkerou/favicon", + "revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93", + "revisionTime": "2017-07-10T14:05:20Z" + }, { "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", "path": "github.com/ugorji/go/codec", @@ -90,6 +96,12 @@ "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revisionTime": "2016-10-18T08:54:36Z" }, + { + "checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=", + "path": "golang.org/x/sync/errgroup", + "revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690", + "revisionTime": "2017-07-19T03:38:01Z" + }, { "checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=", "path": "golang.org/x/sys/unix", From 9a4ecc87d6f8272b8e2450f9c0ab12d3e814521f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Nov 2017 13:24:51 +0800 Subject: [PATCH 121/912] format some codes style (#1165) --- context.go | 13 +++++-------- gin.go | 4 ++-- json/json.go | 4 +--- json/jsoniter.go | 4 +--- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/context.go b/context.go index 5b67dcc..1ed65fe 100644 --- a/context.go +++ b/context.go @@ -33,9 +33,7 @@ const ( MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm ) -const ( - abortIndex int8 = math.MaxInt8 / 2 -) +const abortIndex int8 = math.MaxInt8 / 2 // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. @@ -105,8 +103,7 @@ func (c *Context) Handler() HandlerFunc { // See example in GitHub. func (c *Context) Next() { c.index++ - s := int8(len(c.handlers)) - for ; c.index < s; c.index++ { + for s := int8(len(c.handlers)); c.index < s; c.index++ { c.handlers[c.index](c) } } @@ -521,11 +518,11 @@ func (c *Context) ClientIP() string { clientIP = clientIP[0:index] } clientIP = strings.TrimSpace(clientIP) - if len(clientIP) > 0 { + if clientIP != "" { return clientIP } clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) - if len(clientIP) > 0 { + if clientIP != "" { return clientIP } } @@ -588,7 +585,7 @@ func (c *Context) Status(code int) { // It writes a header in the response. // If value == "", this method removes the header `c.Writer.Header().Del(key)` func (c *Context) Header(key, value string) { - if len(value) == 0 { + if value == "" { c.Writer.Header().Del(key) } else { c.Writer.Header().Set(key, value) diff --git a/gin.go b/gin.go index 4857f3a..b59712b 100644 --- a/gin.go +++ b/gin.go @@ -403,8 +403,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/json/json.go b/json/json.go index d2d0f8b..aa76aa3 100644 --- a/json/json.go +++ b/json/json.go @@ -6,9 +6,7 @@ package json -import ( - "encoding/json" -) +import "encoding/json" var ( Marshal = json.Marshal diff --git a/json/jsoniter.go b/json/jsoniter.go index 65deee5..ffe1424 100644 --- a/json/jsoniter.go +++ b/json/jsoniter.go @@ -6,9 +6,7 @@ package json -import ( - "github.com/json-iterator/go" -) +import "github.com/json-iterator/go" var ( json = jsoniter.ConfigCompatibleWithStandardLibrary From 80f691159fa5821b654b4b78cee8a5cb4316fcd7 Mon Sep 17 00:00:00 2001 From: Andrii Bubis Date: Sun, 12 Nov 2017 07:37:32 +0200 Subject: [PATCH 122/912] Added simple testing documentation and examples (#1156) --- README.md | 46 +++++++++++++++++++++++++++++++++++++ examples/basic/main.go | 7 +++++- examples/basic/main_test.go | 20 ++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 examples/basic/main_test.go diff --git a/README.md b/README.md index 6b8cdd7..bffcce3 100644 --- a/README.md +++ b/README.md @@ -1344,6 +1344,52 @@ func main() { } ``` +## Testing + +The `net/http/httptest` package is preferable way for HTTP testing. + +```go +package main + +func setupRouter() *gin.Engine { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + return r +} + +func main() { + r := setupRouter() + r.Run(":8080") +} +``` + +Test for code example above: + +```go +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPingRoute(t *testing.T) { + router := setupRouter() + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/ping", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "pong", w.Body.String()) +} +``` + ## Users [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. diff --git a/examples/basic/main.go b/examples/basic/main.go index 984c06a..473c6a0 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 0000000..61203d6 --- /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()) +} From ae9f03e6e80212e45427f811683f2a512cc8f506 Mon Sep 17 00:00:00 2001 From: Richard Lee Date: Sun, 12 Nov 2017 13:56:59 +0800 Subject: [PATCH 123/912] Fix up syntax error in README (#1155) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bffcce3..7d3b919 100644 --- a/README.md +++ b/README.md @@ -472,7 +472,7 @@ func main() { // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login - if err = c.ShouldBindJSON(&json); err == nil { + if 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 { From 1f377cb847977aeeed30c3e77377c8a9ff7ccff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 21 Nov 2017 09:27:57 +0800 Subject: [PATCH 124/912] Add test cases for RunTLS and each mode (#1173) * add RunTLS test cases and add debug/test mode cases * add release mode cases --- fixtures/testdata/cert.pem | 18 ++++++ fixtures/testdata/key.pem | 27 ++++++++ gin_test.go | 128 +++++++++++++++++++++++++++++++------ 3 files changed, 155 insertions(+), 18 deletions(-) create mode 100644 fixtures/testdata/cert.pem create mode 100644 fixtures/testdata/key.pem diff --git a/fixtures/testdata/cert.pem b/fixtures/testdata/cert.pem new file mode 100644 index 0000000..c1d3d63 --- /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 0000000..c2a0181 --- /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/gin_test.go b/gin_test.go index bdf5a9a..57985dc 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,33 @@ 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) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "

Hello world

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

Hello world

", string(resp[:])) + + td() +} + +func TestLoadHTMLGlobUsingTLS(t *testing.T) { + td := setupHTMLGlob(t, DebugMode, true) + // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{Transport: tr} + res, err := client.Get("https://127.0.0.1:9999/test") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp[:])) + + td() +} + func TestLoadHTMLGlobFromFuncMap(t *testing.T) { time.Now() - td := setupHTMLGlob(t) + td := setupHTMLGlob(t, DebugMode, false) res, err := http.Get("http://127.0.0.1:8888/raw") if err != nil { fmt.Println(err) @@ -97,9 +153,6 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { td() } -// func (engine *Engine) LoadHTMLFiles(files ...string) { -// func (engine *Engine) RunTLS(addr string, cert string, key string) error { - func init() { SetMode(TestMode) } @@ -127,7 +180,31 @@ func TestCreateEngine(t *testing.T) { // } func TestLoadHTMLFiles(t *testing.T) { - td := setupHTMLFiles(t) + td := setupHTMLFiles(t, TestMode, false) + res, err := http.Get("http://127.0.0.1:8888/test") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "

Hello world

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

Hello world

", string(resp[:])) + td() +} + +func TestLoadHTMLFiles3(t *testing.T) { + td := setupHTMLFiles(t, ReleaseMode, false) res, err := http.Get("http://127.0.0.1:8888/test") if err != nil { fmt.Println(err) @@ -138,9 +215,28 @@ func TestLoadHTMLFiles(t *testing.T) { td() } +func TestLoadHTMLFilesUsingTLS(t *testing.T) { + td := setupHTMLFiles(t, TestMode, true) + // Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{Transport: tr} + res, err := client.Get("https://127.0.0.1:9999/test") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp[:])) + td() +} + func TestLoadHTMLFilesFuncMap(t *testing.T) { time.Now() - td := setupHTMLFiles(t) + td := setupHTMLFiles(t, TestMode, false) res, err := http.Get("http://127.0.0.1:8888/raw") if err != nil { fmt.Println(err) @@ -152,10 +248,6 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { td() } -func TestLoadHTMLReleaseMode(t *testing.T) { - -} - func TestAddRoute(t *testing.T) { router := New() router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) From 7e5aff8ea7456c90f0e362c1e23a192416cbbab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 21 Nov 2017 10:03:57 +0800 Subject: [PATCH 125/912] simple code and increase coverage (#1174) --- mode.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mode.go b/mode.go index 393e13e..394bebe 100644 --- a/mode.go +++ b/mode.go @@ -39,16 +39,12 @@ var modeName = DebugMode func init() { mode := os.Getenv(ENV_GIN_MODE) - if mode == "" { - SetMode(DebugMode) - } else { - SetMode(mode) - } + SetMode(mode) } func SetMode(value string) { switch value { - case DebugMode: + case DebugMode, "": ginMode = debugCode case ReleaseMode: ginMode = releaseCode From eeb57848cac06c360a3c262837394051018b8f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 21 Nov 2017 21:18:45 +0800 Subject: [PATCH 126/912] update assert param(expect, actual) position (#1177) --- context_test.go | 286 ++++++++++++++++++++++---------------------- gin_test.go | 4 +- logger_test.go | 24 ++-- mode_test.go | 16 +-- path_test.go | 4 +- recovery_test.go | 4 +- routergroup_test.go | 20 ++-- routes_test.go | 124 +++++++++---------- utils_test.go | 60 +++++----- 9 files changed, 271 insertions(+), 271 deletions(-) diff --git a/context_test.go b/context_test.go index 8cd05f6..932d9b1 100644 --- a/context_test.go +++ b/context_test.go @@ -180,14 +180,14 @@ func TestContextSetGet(t *testing.T) { c.Set("foo", "bar") value, err := c.Get("foo") - assert.Equal(t, value, "bar") + assert.Equal(t, "bar", value) assert.True(t, err) value, err = c.Get("foo2") assert.Nil(t, value) assert.False(t, err) - assert.Equal(t, c.MustGet("foo"), "bar") + assert.Equal(t, "bar", c.MustGet("foo")) assert.Panics(t, func() { c.MustGet("no_exist") }) } @@ -221,7 +221,7 @@ func TestContextGetString(t *testing.T) { func TestContextSetGetBool(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("bool", true) - assert.Equal(t, true, c.GetBool("bool")) + assert.True(t, c.GetBool("bool")) } func TestContextGetInt(t *testing.T) { @@ -338,26 +338,26 @@ func TestContextQuery(t *testing.T) { value, ok := c.GetQuery("foo") assert.True(t, ok) - assert.Equal(t, value, "bar") - assert.Equal(t, c.DefaultQuery("foo", "none"), "bar") - assert.Equal(t, c.Query("foo"), "bar") + assert.Equal(t, "bar", value) + assert.Equal(t, "bar", c.DefaultQuery("foo", "none")) + assert.Equal(t, "bar", c.Query("foo")) value, ok = c.GetQuery("page") assert.True(t, ok) - assert.Equal(t, value, "10") - assert.Equal(t, c.DefaultQuery("page", "0"), "10") - assert.Equal(t, c.Query("page"), "10") + assert.Equal(t, "10", value) + assert.Equal(t, "10", c.DefaultQuery("page", "0")) + assert.Equal(t, "10", c.Query("page")) value, ok = c.GetQuery("id") assert.True(t, ok) assert.Empty(t, value) - assert.Equal(t, c.DefaultQuery("id", "nada"), "") + assert.Empty(t, c.DefaultQuery("id", "nada")) assert.Empty(t, c.Query("id")) value, ok = c.GetQuery("NoKey") assert.False(t, ok) assert.Empty(t, value) - assert.Equal(t, c.DefaultQuery("NoKey", "nada"), "nada") + assert.Equal(t, "nada", c.DefaultQuery("NoKey", "nada")) assert.Empty(t, c.Query("NoKey")) // postform should not mess @@ -373,29 +373,29 @@ func TestContextQueryAndPostForm(t *testing.T) { c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) - assert.Equal(t, c.DefaultPostForm("foo", "none"), "bar") - assert.Equal(t, c.PostForm("foo"), "bar") + assert.Equal(t, "bar", c.DefaultPostForm("foo", "none")) + assert.Equal(t, "bar", c.PostForm("foo")) assert.Empty(t, c.Query("foo")) value, ok := c.GetPostForm("page") assert.True(t, ok) - assert.Equal(t, value, "11") - assert.Equal(t, c.DefaultPostForm("page", "0"), "11") - assert.Equal(t, c.PostForm("page"), "11") - assert.Equal(t, c.Query("page"), "") + assert.Equal(t, "11", value) + assert.Equal(t, "11", c.DefaultPostForm("page", "0")) + assert.Equal(t, "11", c.PostForm("page")) + assert.Empty(t, c.Query("page")) value, ok = c.GetPostForm("both") assert.True(t, ok) assert.Empty(t, value) assert.Empty(t, c.PostForm("both")) - assert.Equal(t, c.DefaultPostForm("both", "nothing"), "") - assert.Equal(t, c.Query("both"), "GET") + assert.Empty(t, c.DefaultPostForm("both", "nothing")) + assert.Equal(t, "GET", c.Query("both"), "GET") value, ok = c.GetQuery("id") assert.True(t, ok) - assert.Equal(t, value, "main") - assert.Equal(t, c.DefaultPostForm("id", "000"), "000") - assert.Equal(t, c.Query("id"), "main") + assert.Equal(t, "main", value) + assert.Equal(t, "000", c.DefaultPostForm("id", "000")) + assert.Equal(t, "main", c.Query("id")) assert.Empty(t, c.PostForm("id")) value, ok = c.GetQuery("NoKey") @@ -404,8 +404,8 @@ func TestContextQueryAndPostForm(t *testing.T) { value, ok = c.GetPostForm("NoKey") assert.False(t, ok) assert.Empty(t, value) - assert.Equal(t, c.DefaultPostForm("NoKey", "nada"), "nada") - assert.Equal(t, c.DefaultQuery("NoKey", "nothing"), "nothing") + assert.Equal(t, "nada", c.DefaultPostForm("NoKey", "nada")) + assert.Equal(t, "nothing", c.DefaultQuery("NoKey", "nothing")) assert.Empty(t, c.PostForm("NoKey")) assert.Empty(t, c.Query("NoKey")) @@ -417,11 +417,11 @@ func TestContextQueryAndPostForm(t *testing.T) { Array []string `form:"array[]"` } assert.NoError(t, c.Bind(&obj)) - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.ID, "main") - assert.Equal(t, obj.Page, 11) - assert.Equal(t, obj.Both, "") - assert.Equal(t, obj.Array, []string{"first", "second"}) + assert.Equal(t, "bar", obj.Foo, "bar") + assert.Equal(t, "main", obj.ID, "main") + assert.Equal(t, 11, obj.Page, 11) + assert.Empty(t, obj.Both) + assert.Equal(t, []string{"first", "second"}, obj.Array) values, ok := c.GetQueryArray("array[]") assert.True(t, ok) @@ -456,37 +456,37 @@ func TestContextPostFormMultipart(t *testing.T) { BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"` } assert.NoError(t, c.Bind(&obj)) - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "10") - assert.Equal(t, obj.BarAsInt, 10) - assert.Equal(t, obj.Array, []string{"first", "second"}) - assert.Equal(t, obj.ID, "") - assert.Equal(t, obj.TimeLocal.Format("02/01/2006 15:04"), "31/12/2016 14:55") - assert.Equal(t, obj.TimeLocal.Location(), time.Local) - assert.Equal(t, obj.TimeUTC.Format("02/01/2006 15:04"), "31/12/2016 14:55") - assert.Equal(t, obj.TimeUTC.Location(), time.UTC) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "10", obj.Bar) + assert.Equal(t, 10, obj.BarAsInt) + assert.Equal(t, []string{"first", "second"}, obj.Array) + assert.Empty(t, obj.ID) + assert.Equal(t, "31/12/2016 14:55", obj.TimeLocal.Format("02/01/2006 15:04")) + assert.Equal(t, time.Local, obj.TimeLocal.Location()) + assert.Equal(t, "31/12/2016 14:55", obj.TimeUTC.Format("02/01/2006 15:04")) + assert.Equal(t, time.UTC, obj.TimeUTC.Location()) loc, _ := time.LoadLocation("Asia/Tokyo") - assert.Equal(t, obj.TimeLocation.Format("02/01/2006 15:04"), "31/12/2016 14:55") - assert.Equal(t, obj.TimeLocation.Location(), loc) + assert.Equal(t, "31/12/2016 14:55", obj.TimeLocation.Format("02/01/2006 15:04")) + assert.Equal(t, loc, obj.TimeLocation.Location()) assert.True(t, obj.BlankTime.IsZero()) value, ok := c.GetQuery("foo") assert.False(t, ok) assert.Empty(t, value) assert.Empty(t, c.Query("bar")) - assert.Equal(t, c.DefaultQuery("id", "nothing"), "nothing") + assert.Equal(t, "nothing", c.DefaultQuery("id", "nothing")) value, ok = c.GetPostForm("foo") assert.True(t, ok) - assert.Equal(t, value, "bar") - assert.Equal(t, c.PostForm("foo"), "bar") + assert.Equal(t, "bar", value) + assert.Equal(t, "bar", c.PostForm("foo")) value, ok = c.GetPostForm("array") assert.True(t, ok) - assert.Equal(t, value, "first") - assert.Equal(t, c.PostForm("array"), "first") + assert.Equal(t, "first", value) + assert.Equal(t, "first", c.PostForm("array")) - assert.Equal(t, c.DefaultPostForm("bar", "nothing"), "10") + assert.Equal(t, "10", c.DefaultPostForm("bar", "nothing")) value, ok = c.GetPostForm("id") assert.True(t, ok) @@ -497,7 +497,7 @@ func TestContextPostFormMultipart(t *testing.T) { value, ok = c.GetPostForm("nokey") assert.False(t, ok) assert.Empty(t, value) - assert.Equal(t, c.DefaultPostForm("nokey", "nothing"), "nothing") + assert.Equal(t, "nothing", c.DefaultPostForm("nokey", "nothing")) values, ok := c.GetPostFormArray("array") assert.True(t, ok) @@ -519,13 +519,13 @@ func TestContextPostFormMultipart(t *testing.T) { func TestContextSetCookie(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.SetCookie("user", "gin", 1, "/", "localhost", true, true) - assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") + assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie")) } func TestContextSetCookiePathEmpty(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.SetCookie("user", "gin", 1, "", "localhost", true, true) - assert.Equal(t, c.Writer.Header().Get("Set-Cookie"), "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") + assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie")) } func TestContextGetCookie(t *testing.T) { @@ -533,17 +533,17 @@ func TestContextGetCookie(t *testing.T) { c.Request, _ = http.NewRequest("GET", "/get", nil) c.Request.Header.Set("Cookie", "user=gin") cookie, _ := c.Cookie("user") - assert.Equal(t, cookie, "gin") + assert.Equal(t, "gin", cookie) _, err := c.Cookie("nokey") assert.Error(t, err) } func TestContextBodyAllowedForStatus(t *testing.T) { - assert.Equal(t, false, bodyAllowedForStatus(102)) - assert.Equal(t, false, bodyAllowedForStatus(204)) - assert.Equal(t, false, bodyAllowedForStatus(304)) - assert.Equal(t, true, bodyAllowedForStatus(500)) + assert.False(t, false, bodyAllowedForStatus(102)) + assert.False(t, false, bodyAllowedForStatus(204)) + assert.False(t, false, bodyAllowedForStatus(304)) + assert.True(t, true, bodyAllowedForStatus(500)) } type TestPanicRender struct { @@ -589,7 +589,7 @@ func TestContextRenderNoContentJSON(t *testing.T) { c.JSON(204, H{"foo": "bar"}) assert.Equal(t, 204, w.Code) - assert.Equal(t, "", w.Body.String()) + assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -616,7 +616,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) { c.JSON(204, H{"foo": "bar"}) assert.Equal(t, 204, w.Code) - assert.Equal(t, "", w.Body.String()) + assert.Empty(t, w.Body.String()) assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") } @@ -628,7 +628,7 @@ func TestContextRenderIndentedJSON(t *testing.T) { c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) - assert.Equal(t, w.Code, 201) + assert.Equal(t, 201, w.Code) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -641,7 +641,7 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) assert.Equal(t, 204, w.Code) - assert.Equal(t, "", w.Body.String()) + assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -654,9 +654,9 @@ func TestContextRenderSecureJSON(t *testing.T) { router.SecureJsonPrefix("&&&START&&&") c.SecureJSON(201, []string{"foo", "bar"}) - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "&&&START&&&[\"foo\",\"bar\"]") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 @@ -667,8 +667,8 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { c.SecureJSON(204, []string{"foo", "bar"}) assert.Equal(t, 204, w.Code) - assert.Equal(t, "", w.Body.String()) - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8") + assert.Empty(t, w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // Tests that the response executes the templates @@ -681,9 +681,9 @@ func TestContextRenderHTML(t *testing.T) { c.HTML(201, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "Hello alexandernyquist") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "Hello alexandernyquist", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // Tests that no HTML is rendered if code is 204 @@ -696,8 +696,8 @@ func TestContextRenderNoContentHTML(t *testing.T) { c.HTML(204, "t", H{"name": "alexandernyquist"}) assert.Equal(t, 204, w.Code) - assert.Equal(t, "", w.Body.String()) - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") + assert.Empty(t, w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // TestContextXML tests that the response is serialized as XML @@ -708,9 +708,9 @@ func TestContextRenderXML(t *testing.T) { c.XML(201, H{"foo": "bar"}) - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "bar") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "bar", w.Body.String()) + assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // Tests that no XML is rendered if code is 204 @@ -721,8 +721,8 @@ func TestContextRenderNoContentXML(t *testing.T) { c.XML(204, H{"foo": "bar"}) assert.Equal(t, 204, w.Code) - assert.Equal(t, "", w.Body.String()) - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/xml; charset=utf-8") + assert.Empty(t, w.Body.String()) + assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // TestContextString tests that the response is returned @@ -733,9 +733,9 @@ func TestContextRenderString(t *testing.T) { c.String(201, "test %s %d", "string", 2) - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "test string 2") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "test string 2", w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // Tests that no String is rendered if code is 204 @@ -746,8 +746,8 @@ func TestContextRenderNoContentString(t *testing.T) { c.String(204, "test %s %d", "string", 2) assert.Equal(t, 204, w.Code) - assert.Equal(t, "", w.Body.String()) - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") + assert.Empty(t, w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // TestContextString tests that the response is returned @@ -759,9 +759,9 @@ func TestContextRenderHTMLString(t *testing.T) { c.Header("Content-Type", "text/html; charset=utf-8") c.String(201, "%s %d", "string", 3) - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "string 3") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "string 3", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // Tests that no HTML String is rendered if code is 204 @@ -773,8 +773,8 @@ func TestContextRenderNoContentHTMLString(t *testing.T) { c.String(204, "%s %d", "string", 3) assert.Equal(t, 204, w.Code) - assert.Equal(t, "", w.Body.String()) - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") + assert.Empty(t, w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // TestContextData tests that the response can be written from `bytesting` @@ -785,9 +785,9 @@ func TestContextRenderData(t *testing.T) { c.Data(201, "text/csv", []byte(`foo,bar`)) - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "foo,bar") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "foo,bar", w.Body.String()) + assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) } // Tests that no Custom Data is rendered if code is 204 @@ -798,8 +798,8 @@ func TestContextRenderNoContentData(t *testing.T) { c.Data(204, "text/csv", []byte(`foo,bar`)) assert.Equal(t, 204, w.Code) - assert.Equal(t, "", w.Body.String()) - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/csv") + assert.Empty(t, w.Body.String()) + assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) } func TestContextRenderSSE(t *testing.T) { @@ -826,9 +826,9 @@ func TestContextRenderFile(t *testing.T) { c.Request, _ = http.NewRequest("GET", "/", nil) c.File("./gin.go") - assert.Equal(t, w.Code, 200) + assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // TestContextRenderYAML tests that the response is serialized as YAML @@ -839,9 +839,9 @@ func TestContextRenderYAML(t *testing.T) { c.YAML(201, H{"foo": "bar"}) - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Body.String(), "foo: bar\n") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/x-yaml; charset=utf-8") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "foo: bar\n", w.Body.String()) + assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } func TestContextHeaders(t *testing.T) { @@ -849,13 +849,13 @@ func TestContextHeaders(t *testing.T) { c.Header("Content-Type", "text/plain") c.Header("X-Custom", "value") - assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/plain") - assert.Equal(t, c.Writer.Header().Get("X-Custom"), "value") + assert.Equal(t, "text/plain", c.Writer.Header().Get("Content-Type")) + assert.Equal(t, "value", c.Writer.Header().Get("X-Custom")) c.Header("Content-Type", "text/html") c.Header("X-Custom", "") - assert.Equal(t, c.Writer.Header().Get("Content-Type"), "text/html") + assert.Equal(t, "text/html", c.Writer.Header().Get("Content-Type")) _, exist := c.Writer.Header()["X-Custom"] assert.False(t, exist) } @@ -871,8 +871,8 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) { c.Redirect(301, "/path") c.Writer.WriteHeaderNow() - assert.Equal(t, w.Code, 301) - assert.Equal(t, w.Header().Get("Location"), "/path") + assert.Equal(t, 301, w.Code) + assert.Equal(t, "/path", w.Header().Get("Location")) } func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { @@ -883,8 +883,8 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { c.Redirect(302, "http://google.com") c.Writer.WriteHeaderNow() - assert.Equal(t, w.Code, 302) - assert.Equal(t, w.Header().Get("Location"), "http://google.com") + assert.Equal(t, 302, w.Code) + assert.Equal(t, "http://google.com", w.Header().Get("Location")) } func TestContextRenderRedirectWith201(t *testing.T) { @@ -895,8 +895,8 @@ func TestContextRenderRedirectWith201(t *testing.T) { c.Redirect(201, "/resource") c.Writer.WriteHeaderNow() - assert.Equal(t, w.Code, 201) - assert.Equal(t, w.Header().Get("Location"), "/resource") + assert.Equal(t, 201, w.Code) + assert.Equal(t, "/resource", w.Header().Get("Location")) } func TestContextRenderRedirectAll(t *testing.T) { @@ -977,8 +977,8 @@ func TestContextNegotiationFormat(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) assert.Panics(t, func() { c.NegotiateFormat() }) - assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON) - assert.Equal(t, c.NegotiateFormat(MIMEHTML, MIMEJSON), MIMEHTML) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) + assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON)) } func TestContextNegotiationFormatWithAccept(t *testing.T) { @@ -986,9 +986,9 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) { c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") - assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEXML) - assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEHTML) - assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") + assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML)) + assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML)) + assert.Empty(t, c.NegotiateFormat(MIMEJSON)) } func TestContextNegotiationFormatCustum(t *testing.T) { @@ -999,9 +999,9 @@ func TestContextNegotiationFormatCustum(t *testing.T) { c.Accepted = nil c.SetAccepted(MIMEJSON, MIMEXML) - assert.Equal(t, c.NegotiateFormat(MIMEJSON, MIMEXML), MIMEJSON) - assert.Equal(t, c.NegotiateFormat(MIMEXML, MIMEHTML), MIMEXML) - assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) + assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML)) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) } func TestContextIsAborted(t *testing.T) { @@ -1027,9 +1027,9 @@ func TestContextAbortWithStatus(t *testing.T) { c.index = 4 c.AbortWithStatus(401) - assert.Equal(t, c.index, abortIndex) - assert.Equal(t, c.Writer.Status(), 401) - assert.Equal(t, w.Code, 401) + assert.Equal(t, abortIndex, c.index) + assert.Equal(t, 401, c.Writer.Status()) + assert.Equal(t, 401, w.Code) assert.True(t, c.IsAborted()) } @@ -1049,13 +1049,13 @@ func TestContextAbortWithStatusJSON(t *testing.T) { c.AbortWithStatusJSON(415, in) - assert.Equal(t, c.index, abortIndex) - assert.Equal(t, c.Writer.Status(), 415) - assert.Equal(t, w.Code, 415) + assert.Equal(t, abortIndex, c.index) + assert.Equal(t, 415, c.Writer.Status()) + assert.Equal(t, 415, w.Code) assert.True(t, c.IsAborted()) contentType := w.Header().Get("Content-Type") - assert.Equal(t, contentType, "application/json; charset=utf-8") + assert.Equal(t, "application/json; charset=utf-8", contentType) buf := new(bytes.Buffer) buf.ReadFrom(w.Body) @@ -1069,7 +1069,7 @@ func TestContextError(t *testing.T) { c.Error(errors.New("first error")) assert.Len(t, c.Errors, 1) - assert.Equal(t, c.Errors.String(), "Error #01: first error\n") + assert.Equal(t, "Error #01: first error\n", c.Errors.String()) c.Error(&Error{ Err: errors.New("second error"), @@ -1078,13 +1078,13 @@ func TestContextError(t *testing.T) { }) assert.Len(t, c.Errors, 2) - assert.Equal(t, c.Errors[0].Err, errors.New("first error")) + assert.Equal(t, errors.New("first error"), c.Errors[0].Err) assert.Nil(t, c.Errors[0].Meta) - assert.Equal(t, c.Errors[0].Type, ErrorTypePrivate) + assert.Equal(t, ErrorTypePrivate, c.Errors[0].Type) - assert.Equal(t, c.Errors[1].Err, errors.New("second error")) - assert.Equal(t, c.Errors[1].Meta, "some data 2") - assert.Equal(t, c.Errors[1].Type, ErrorTypePublic) + assert.Equal(t, errors.New("second error"), c.Errors[1].Err) + assert.Equal(t, "some data 2", c.Errors[1].Meta) + assert.Equal(t, ErrorTypePublic, c.Errors[1].Type) assert.Equal(t, c.Errors.Last(), c.Errors[1]) @@ -1102,12 +1102,12 @@ func TestContextTypedError(t *testing.T) { c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) for _, err := range c.Errors.ByType(ErrorTypePublic) { - assert.Equal(t, err.Type, ErrorTypePublic) + assert.Equal(t, ErrorTypePublic, err.Type) } for _, err := range c.Errors.ByType(ErrorTypePrivate) { - assert.Equal(t, err.Type, ErrorTypePrivate) + assert.Equal(t, ErrorTypePrivate, err.Type) } - assert.Equal(t, c.Errors.Errors(), []string{"externo 0", "interno 0"}) + assert.Equal(t, []string{"externo 0", "interno 0"}, c.Errors.Errors()) } func TestContextAbortWithError(t *testing.T) { @@ -1116,8 +1116,8 @@ func TestContextAbortWithError(t *testing.T) { c.AbortWithError(401, errors.New("bad input")).SetMeta("some input") - assert.Equal(t, w.Code, 401) - assert.Equal(t, c.index, abortIndex) + assert.Equal(t, 401, w.Code) + assert.Equal(t, abortIndex, c.index) assert.True(t, c.IsAborted()) } @@ -1148,7 +1148,7 @@ func TestContextClientIP(t *testing.T) { // no port c.Request.RemoteAddr = "50.50.50.50" - assert.Equal(t, "", c.ClientIP()) + assert.Empty(t, c.ClientIP()) } func TestContextContentType(t *testing.T) { @@ -1156,7 +1156,7 @@ func TestContextContentType(t *testing.T) { c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("Content-Type", "application/json; charset=utf-8") - assert.Equal(t, c.ContentType(), "application/json") + assert.Equal(t, "application/json", c.ContentType()) } func TestContextAutoBindJSON(t *testing.T) { @@ -1169,8 +1169,8 @@ func TestContextAutoBindJSON(t *testing.T) { Bar string `json:"bar"` } assert.NoError(t, c.Bind(&obj)) - assert.Equal(t, obj.Bar, "foo") - assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) assert.Empty(t, c.Errors) } @@ -1186,9 +1186,9 @@ func TestContextBindWithJSON(t *testing.T) { Bar string `json:"bar"` } assert.NoError(t, c.BindJSON(&obj)) - assert.Equal(t, obj.Bar, "foo") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, w.Body.Len(), 0) + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, 0, w.Body.Len()) } func TestContextBindWithQuery(t *testing.T) { @@ -1224,7 +1224,7 @@ func TestContextBadAutoBind(t *testing.T) { assert.Empty(t, obj.Bar) assert.Empty(t, obj.Foo) - assert.Equal(t, w.Code, 400) + assert.Equal(t, 400, w.Code) assert.True(t, c.IsAborted()) } @@ -1238,8 +1238,8 @@ func TestContextAutoShouldBindJSON(t *testing.T) { Bar string `json:"bar"` } assert.NoError(t, c.ShouldBind(&obj)) - assert.Equal(t, obj.Bar, "foo") - assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) assert.Empty(t, c.Errors) } @@ -1255,9 +1255,9 @@ func TestContextShouldBindWithJSON(t *testing.T) { Bar string `json:"bar"` } assert.NoError(t, c.ShouldBindJSON(&obj)) - assert.Equal(t, obj.Bar, "foo") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, w.Body.Len(), 0) + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, 0, w.Body.Len()) } func TestContextShouldBindWithQuery(t *testing.T) { @@ -1307,7 +1307,7 @@ func TestContextGolangContext(t *testing.T) { assert.Nil(t, c.Value("foo")) c.Set("foo", "bar") - assert.Equal(t, c.Value("foo"), "bar") + assert.Equal(t, "bar", c.Value("foo")) assert.Nil(t, c.Value(1)) } @@ -1339,7 +1339,7 @@ func TestGetRequestHeaderValue(t *testing.T) { c.Request.Header.Set("Gin-Version", "1.0.0") assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version")) - assert.Equal(t, "", c.GetHeader("Connection")) + assert.Empty(t, c.GetHeader("Connection")) } func TestContextGetRawData(t *testing.T) { diff --git a/gin_test.go b/gin_test.go index 57985dc..3ac6057 100644 --- a/gin_test.go +++ b/gin_test.go @@ -170,12 +170,12 @@ func TestCreateEngine(t *testing.T) { // router.LoadHTMLGlob("*.testtmpl") // r := router.HTMLRender.(render.HTMLDebug) // assert.Empty(t, r.Files) -// assert.Equal(t, r.Glob, "*.testtmpl") +// assert.Equal(t, "*.testtmpl", r.Glob) // // router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl") // r = router.HTMLRender.(render.HTMLDebug) // assert.Empty(t, r.Glob) -// assert.Equal(t, r.Files, []string{"index.html", "login.html"}) +// assert.Equal(t, []string{"index.html", "login.html"}, r.Files) // SetMode(TestMode) // } diff --git a/logger_test.go b/logger_test.go index 62c1366..74a9659 100644 --- a/logger_test.go +++ b/logger_test.go @@ -82,21 +82,21 @@ func TestLogger(t *testing.T) { } func TestColorForMethod(t *testing.T) { - assert.Equal(t, colorForMethod("GET"), string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), "get should be blue") - assert.Equal(t, colorForMethod("POST"), string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), "post should be cyan") - assert.Equal(t, colorForMethod("PUT"), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "put should be yellow") - assert.Equal(t, colorForMethod("DELETE"), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "delete should be red") - assert.Equal(t, colorForMethod("PATCH"), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "patch should be green") - assert.Equal(t, colorForMethod("HEAD"), string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), "head should be magenta") - assert.Equal(t, colorForMethod("OPTIONS"), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "options should be white") - assert.Equal(t, colorForMethod("TRACE"), string([]byte{27, 91, 48, 109}), "trace is not defined and should be the reset color") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta") + assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForMethod("OPTIONS"), "options should be white") + assert.Equal(t, string([]byte{27, 91, 48, 109}), colorForMethod("TRACE"), "trace is not defined and should be the reset color") } func TestColorForStatus(t *testing.T) { - assert.Equal(t, colorForStatus(200), string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), "2xx should be green") - assert.Equal(t, colorForStatus(301), string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), "3xx should be white") - assert.Equal(t, colorForStatus(404), string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), "4xx should be yellow") - assert.Equal(t, colorForStatus(2), string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), "other things should be red") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(200), "2xx should be green") + assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(301), "3xx should be white") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(404), "4xx should be yellow") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") } func TestErrorLogger(t *testing.T) { diff --git a/mode_test.go b/mode_test.go index f3b88a1..7eaca82 100644 --- a/mode_test.go +++ b/mode_test.go @@ -17,21 +17,21 @@ func init() { } func TestSetMode(t *testing.T) { - assert.Equal(t, ginMode, testCode) - assert.Equal(t, Mode(), TestMode) + assert.Equal(t, testCode, ginMode) + assert.Equal(t, TestMode, Mode()) os.Unsetenv(ENV_GIN_MODE) SetMode(DebugMode) - assert.Equal(t, ginMode, debugCode) - assert.Equal(t, Mode(), DebugMode) + assert.Equal(t, debugCode, ginMode) + assert.Equal(t, DebugMode, Mode()) SetMode(ReleaseMode) - assert.Equal(t, ginMode, releaseCode) - assert.Equal(t, Mode(), ReleaseMode) + assert.Equal(t, releaseCode, ginMode) + assert.Equal(t, ReleaseMode, Mode()) SetMode(TestMode) - assert.Equal(t, ginMode, testCode) - assert.Equal(t, Mode(), TestMode) + assert.Equal(t, testCode, ginMode) + assert.Equal(t, TestMode, Mode()) assert.Panics(t, func() { SetMode("unknown") }) } diff --git a/path_test.go b/path_test.go index bf2e5f6..4a6d945 100644 --- a/path_test.go +++ b/path_test.go @@ -67,8 +67,8 @@ var cleanTests = []struct { func TestPathClean(t *testing.T) { for _, test := range cleanTests { - assert.Equal(t, cleanPath(test.path), test.result) - assert.Equal(t, cleanPath(test.result), test.result) + assert.Equal(t, test.result, cleanPath(test.path)) + assert.Equal(t, test.result, cleanPath(test.result)) } } diff --git a/recovery_test.go b/recovery_test.go index 4545ba3..de3b62a 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -22,7 +22,7 @@ func TestPanicInHandler(t *testing.T) { // RUN w := performRequest(router, "GET", "/recovery") // TEST - assert.Equal(t, w.Code, 500) + assert.Equal(t, 500, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "TestPanicInHandler") @@ -39,5 +39,5 @@ func TestPanicWithAbort(t *testing.T) { // RUN w := performRequest(router, "GET", "/recovery") // TEST - assert.Equal(t, w.Code, 400) + assert.Equal(t, 400, w.Code) } diff --git a/routergroup_test.go b/routergroup_test.go index b0589b5..a362e23 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -20,15 +20,15 @@ func TestRouterGroupBasic(t *testing.T) { group.Use(func(c *Context) {}) assert.Len(t, group.Handlers, 2) - assert.Equal(t, group.BasePath(), "/hola") - assert.Equal(t, group.engine, router) + assert.Equal(t, "/hola", group.BasePath()) + assert.Equal(t, router, group.engine) group2 := group.Group("manu") group2.Use(func(c *Context) {}, func(c *Context) {}) assert.Len(t, group2.Handlers, 4) - assert.Equal(t, group2.BasePath(), "/hola/manu") - assert.Equal(t, group2.engine, router) + assert.Equal(t, "/hola/manu", group2.BasePath()) + assert.Equal(t, router, group2.engine) } func TestRouterGroupBasicHandle(t *testing.T) { @@ -44,10 +44,10 @@ func TestRouterGroupBasicHandle(t *testing.T) { func performRequestInGroup(t *testing.T, method string) { router := New() v1 := router.Group("v1", func(c *Context) {}) - assert.Equal(t, v1.BasePath(), "/v1") + assert.Equal(t, "/v1", v1.BasePath()) login := v1.Group("/login/", func(c *Context) {}, func(c *Context) {}) - assert.Equal(t, login.BasePath(), "/v1/login/") + assert.Equal(t, "/v1/login/", login.BasePath()) handler := func(c *Context) { c.String(400, "the method was %s and index %d", c.Request.Method, c.index) @@ -80,12 +80,12 @@ func performRequestInGroup(t *testing.T, method string) { } w := performRequest(router, method, "/v1/login/test") - assert.Equal(t, w.Code, 400) - assert.Equal(t, w.Body.String(), "the method was "+method+" and index 3") + assert.Equal(t, 400, w.Code) + assert.Equal(t, "the method was "+method+" and index 3", w.Body.String()) w = performRequest(router, method, "/v1/test") - assert.Equal(t, w.Code, 400) - assert.Equal(t, w.Body.String(), "the method was "+method+" and index 1") + assert.Equal(t, 400, w.Code) + assert.Equal(t, "the method was "+method+" and index 1", w.Body.String()) } func TestRouterGroupInvalidStatic(t *testing.T) { diff --git a/routes_test.go b/routes_test.go index b44b643..8129390 100644 --- a/routes_test.go +++ b/routes_test.go @@ -36,7 +36,7 @@ func testRouteOK(method string, t *testing.T) { w := performRequest(r, method, "/test") assert.True(t, passed) - assert.Equal(t, w.Code, http.StatusOK) + assert.Equal(t, http.StatusOK, w.Code) performRequest(r, method, "/test2") assert.True(t, passedAny) @@ -53,7 +53,7 @@ func testRouteNotOK(method string, t *testing.T) { w := performRequest(router, method, "/test") assert.False(t, passed) - assert.Equal(t, w.Code, http.StatusNotFound) + assert.Equal(t, http.StatusNotFound, w.Code) } // TestSingleRouteOK tests that POST route is correctly invoked. @@ -74,7 +74,7 @@ func testRouteNotOK2(method string, t *testing.T) { w := performRequest(router, method, "/test") assert.False(t, passed) - assert.Equal(t, w.Code, http.StatusMethodNotAllowed) + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) } func TestRouterMethod(t *testing.T) { @@ -93,8 +93,8 @@ func TestRouterMethod(t *testing.T) { w := performRequest(router, "PUT", "/hey") - assert.Equal(t, w.Code, 200) - assert.Equal(t, w.Body.String(), "called") + assert.Equal(t, 200, w.Code) + assert.Equal(t, "called", w.Body.String()) } func TestRouterGroupRouteOK(t *testing.T) { @@ -143,43 +143,43 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { router.PUT("/path4/", func(c *Context) {}) w := performRequest(router, "GET", "/path/") - assert.Equal(t, w.Header().Get("Location"), "/path") - assert.Equal(t, w.Code, 301) + assert.Equal(t, "/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) w = performRequest(router, "GET", "/path2") - assert.Equal(t, w.Header().Get("Location"), "/path2/") - assert.Equal(t, w.Code, 301) + assert.Equal(t, "/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) w = performRequest(router, "POST", "/path3/") - assert.Equal(t, w.Header().Get("Location"), "/path3") - assert.Equal(t, w.Code, 307) + assert.Equal(t, "/path3", w.Header().Get("Location")) + assert.Equal(t, 307, w.Code) w = performRequest(router, "PUT", "/path4") - assert.Equal(t, w.Header().Get("Location"), "/path4/") - assert.Equal(t, w.Code, 307) + assert.Equal(t, "/path4/", w.Header().Get("Location")) + assert.Equal(t, 307, w.Code) w = performRequest(router, "GET", "/path") - assert.Equal(t, w.Code, 200) + assert.Equal(t, 200, w.Code) w = performRequest(router, "GET", "/path2/") - assert.Equal(t, w.Code, 200) + assert.Equal(t, 200, w.Code) w = performRequest(router, "POST", "/path3") - assert.Equal(t, w.Code, 200) + assert.Equal(t, 200, w.Code) w = performRequest(router, "PUT", "/path4/") - assert.Equal(t, w.Code, 200) + assert.Equal(t, 200, w.Code) router.RedirectTrailingSlash = false w = performRequest(router, "GET", "/path/") - assert.Equal(t, w.Code, 404) + assert.Equal(t, 404, w.Code) w = performRequest(router, "GET", "/path2") - assert.Equal(t, w.Code, 404) + assert.Equal(t, 404, w.Code) w = performRequest(router, "POST", "/path3/") - assert.Equal(t, w.Code, 404) + assert.Equal(t, 404, w.Code) w = performRequest(router, "PUT", "/path4") - assert.Equal(t, w.Code, 404) + assert.Equal(t, 404, w.Code) } func TestRouteRedirectFixedPath(t *testing.T) { @@ -193,20 +193,20 @@ func TestRouteRedirectFixedPath(t *testing.T) { router.POST("/Path4/", func(c *Context) {}) w := performRequest(router, "GET", "/PATH") - assert.Equal(t, w.Header().Get("Location"), "/path") - assert.Equal(t, w.Code, 301) + assert.Equal(t, "/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) w = performRequest(router, "GET", "/path2") - assert.Equal(t, w.Header().Get("Location"), "/Path2") - assert.Equal(t, w.Code, 301) + assert.Equal(t, "/Path2", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) w = performRequest(router, "POST", "/path3") - assert.Equal(t, w.Header().Get("Location"), "/PATH3") - assert.Equal(t, w.Code, 307) + assert.Equal(t, "/PATH3", w.Header().Get("Location")) + assert.Equal(t, 307, w.Code) w = performRequest(router, "POST", "/path4") - assert.Equal(t, w.Header().Get("Location"), "/Path4/") - assert.Equal(t, w.Code, 307) + assert.Equal(t, "/Path4/", w.Header().Get("Location")) + assert.Equal(t, 307, w.Code) } // TestContextParamsGet tests that a parameter can be parsed from the URL. @@ -236,10 +236,10 @@ func TestRouteParamsByName(t *testing.T) { w := performRequest(router, "GET", "/test/john/smith/is/super/great") - assert.Equal(t, w.Code, 200) - assert.Equal(t, name, "john") - assert.Equal(t, lastName, "smith") - assert.Equal(t, wild, "/is/super/great") + assert.Equal(t, 200, w.Code) + assert.Equal(t, "john", name) + assert.Equal(t, "smith", lastName) + assert.Equal(t, "/is/super/great", wild) } // TestHandleStaticFile - ensure the static file handles properly @@ -265,15 +265,15 @@ func TestRouteStaticFile(t *testing.T) { w2 := performRequest(router, "GET", "/result") assert.Equal(t, w, w2) - assert.Equal(t, w.Code, 200) - assert.Equal(t, w.Body.String(), "Gin Web Framework") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, 200, w.Code) + assert.Equal(t, "Gin Web Framework", w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) w3 := performRequest(router, "HEAD", "/using_static/"+filename) w4 := performRequest(router, "HEAD", "/result") assert.Equal(t, w3, w4) - assert.Equal(t, w3.Code, 200) + assert.Equal(t, 200, w3.Code) } // TestHandleStaticDir - ensure the root/sub dir handles properly @@ -283,9 +283,9 @@ func TestRouteStaticListingDir(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, w.Code, 200) + assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "gin.go") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } // TestHandleHeadToDir - ensure the root/sub dir handles properly @@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, w.Code, 404) + assert.Equal(t, 404, w.Code) assert.NotContains(t, w.Body.String(), "gin.go") } @@ -310,12 +310,12 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { w := performRequest(router, "GET", "/gin.go") - assert.Equal(t, w.Code, 200) + assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "package gin") - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") - assert.Equal(t, w.HeaderMap.Get("Expires"), "Mon, 02 Jan 2006 15:04:05 MST") - assert.Equal(t, w.HeaderMap.Get("x-GIN"), "Gin Framework") + assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.HeaderMap.Get("Expires")) + assert.Equal(t, "Gin Framework", w.HeaderMap.Get("x-GIN")) } func TestRouteNotAllowedEnabled(t *testing.T) { @@ -323,14 +323,14 @@ func TestRouteNotAllowedEnabled(t *testing.T) { router.HandleMethodNotAllowed = true router.POST("/path", func(c *Context) {}) w := performRequest(router, "GET", "/path") - assert.Equal(t, w.Code, http.StatusMethodNotAllowed) + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) w = performRequest(router, "GET", "/path") - assert.Equal(t, w.Body.String(), "responseText") - assert.Equal(t, w.Code, http.StatusTeapot) + assert.Equal(t, "responseText", w.Body.String()) + assert.Equal(t, http.StatusTeapot, w.Code) } func TestRouteNotAllowedDisabled(t *testing.T) { @@ -338,14 +338,14 @@ func TestRouteNotAllowedDisabled(t *testing.T) { router.HandleMethodNotAllowed = false router.POST("/path", func(c *Context) {}) w := performRequest(router, "GET", "/path") - assert.Equal(t, w.Code, 404) + assert.Equal(t, 404, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) w = performRequest(router, "GET", "/path") - assert.Equal(t, w.Body.String(), "404 page not found") - assert.Equal(t, w.Code, 404) + assert.Equal(t, "404 page not found", w.Body.String()) + assert.Equal(t, 404, w.Code) } func TestRouterNotFound(t *testing.T) { @@ -372,9 +372,9 @@ func TestRouterNotFound(t *testing.T) { } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) - assert.Equal(t, w.Code, tr.code) + assert.Equal(t, tr.code, w.Code) if w.Code != 404 { - assert.Equal(t, fmt.Sprint(w.Header().Get("Location")), tr.location) + assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) } } @@ -385,20 +385,20 @@ func TestRouterNotFound(t *testing.T) { notFound = true }) w := performRequest(router, "GET", "/nope") - assert.Equal(t, w.Code, 404) + assert.Equal(t, 404, w.Code) assert.True(t, notFound) // Test other method than GET (want 307 instead of 301) router.PATCH("/path", func(c *Context) {}) w = performRequest(router, "PATCH", "/path/") - assert.Equal(t, w.Code, 307) - assert.Equal(t, fmt.Sprint(w.Header()), "map[Location:[/path]]") + assert.Equal(t, 307, w.Code) + assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header())) // Test special case where no node for the prefix "/" exists router = New() router.GET("/a", func(c *Context) {}) w = performRequest(router, "GET", "/") - assert.Equal(t, w.Code, 404) + assert.Equal(t, 404, w.Code) } func TestRouteRawPath(t *testing.T) { @@ -409,15 +409,15 @@ func TestRouteRawPath(t *testing.T) { name := c.Params.ByName("name") num := c.Params.ByName("num") - assert.Equal(t, c.Param("name"), name) - assert.Equal(t, c.Param("num"), num) + assert.Equal(t, name, c.Param("name")) + assert.Equal(t, num, c.Param("num")) assert.Equal(t, "Some/Other/Project", name) assert.Equal(t, "222", num) }) w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222") - assert.Equal(t, w.Code, 200) + assert.Equal(t, 200, w.Code) } func TestRouteRawPathNoUnescape(t *testing.T) { @@ -429,15 +429,15 @@ func TestRouteRawPathNoUnescape(t *testing.T) { name := c.Params.ByName("name") num := c.Params.ByName("num") - assert.Equal(t, c.Param("name"), name) - assert.Equal(t, c.Param("num"), num) + assert.Equal(t, name, c.Param("name")) + assert.Equal(t, num, c.Param("num")) assert.Equal(t, "Some%2FOther%2FProject", name) assert.Equal(t, "333", num) }) w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333") - assert.Equal(t, w.Code, 200) + assert.Equal(t, 200, w.Code) } func TestRouteServeErrorWithWriteHeader(t *testing.T) { diff --git a/utils_test.go b/utils_test.go index 599172f..3d019e7 100644 --- a/utils_test.go +++ b/utils_test.go @@ -21,8 +21,8 @@ type testStruct struct { } func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { - assert.Equal(t.T, req.Method, "POST") - assert.Equal(t.T, req.URL.Path, "/path") + assert.Equal(t.T, "POST", req.Method) + assert.Equal(t.T, "/path", req.URL.Path) w.WriteHeader(500) fmt.Fprint(w, "hello") } @@ -31,50 +31,50 @@ func TestWrap(t *testing.T) { router := New() router.POST("/path", WrapH(&testStruct{t})) router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) { - assert.Equal(t, req.Method, "GET") - assert.Equal(t, req.URL.Path, "/path2") + assert.Equal(t, "GET", req.Method) + assert.Equal(t, "/path2", req.URL.Path) w.WriteHeader(400) fmt.Fprint(w, "hola!") })) w := performRequest(router, "POST", "/path") - assert.Equal(t, w.Code, 500) - assert.Equal(t, w.Body.String(), "hello") + assert.Equal(t, 500, w.Code) + assert.Equal(t, "hello", w.Body.String()) w = performRequest(router, "GET", "/path2") - assert.Equal(t, w.Code, 400) - assert.Equal(t, w.Body.String(), "hola!") + assert.Equal(t, 400, w.Code) + assert.Equal(t, "hola!", w.Body.String()) } func TestLastChar(t *testing.T) { - assert.Equal(t, lastChar("hola"), uint8('a')) - assert.Equal(t, lastChar("adios"), uint8('s')) + assert.Equal(t, uint8('a'), lastChar("hola")) + assert.Equal(t, uint8('s'), lastChar("adios")) assert.Panics(t, func() { lastChar("") }) } func TestParseAccept(t *testing.T) { parts := parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8") assert.Len(t, parts, 4) - assert.Equal(t, parts[0], "text/html") - assert.Equal(t, parts[1], "application/xhtml+xml") - assert.Equal(t, parts[2], "application/xml") - assert.Equal(t, parts[3], "*/*") + assert.Equal(t, "text/html", parts[0]) + assert.Equal(t, "application/xhtml+xml", parts[1]) + assert.Equal(t, "application/xml", parts[2]) + assert.Equal(t, "*/*", parts[3]) } func TestChooseData(t *testing.T) { A := "a" B := "b" - assert.Equal(t, chooseData(A, B), A) - assert.Equal(t, chooseData(nil, B), B) + assert.Equal(t, A, chooseData(A, B)) + assert.Equal(t, B, chooseData(nil, B)) assert.Panics(t, func() { chooseData(nil, nil) }) } func TestFilterFlags(t *testing.T) { result := filterFlags("text/html ") - assert.Equal(t, result, "text/html") + assert.Equal(t, "text/html", result) result = filterFlags("text/html;") - assert.Equal(t, result, "text/html") + assert.Equal(t, "text/html", result) } func TestFunctionName(t *testing.T) { @@ -86,16 +86,16 @@ func somefunction() { } func TestJoinPaths(t *testing.T) { - assert.Equal(t, joinPaths("", ""), "") - assert.Equal(t, joinPaths("", "/"), "/") - assert.Equal(t, joinPaths("/a", ""), "/a") - assert.Equal(t, joinPaths("/a/", ""), "/a/") - assert.Equal(t, joinPaths("/a/", "/"), "/a/") - assert.Equal(t, joinPaths("/a", "/"), "/a/") - assert.Equal(t, joinPaths("/a", "/hola"), "/a/hola") - assert.Equal(t, joinPaths("/a/", "/hola"), "/a/hola") - assert.Equal(t, joinPaths("/a/", "/hola/"), "/a/hola/") - assert.Equal(t, joinPaths("/a/", "/hola//"), "/a/hola/") + assert.Equal(t, "", joinPaths("", "")) + assert.Equal(t, "/", joinPaths("", "/")) + assert.Equal(t, "/a", joinPaths("/a", "")) + assert.Equal(t, "/a/", joinPaths("/a/", "")) + assert.Equal(t, "/a/", joinPaths("/a/", "/")) + assert.Equal(t, "/a/", joinPaths("/a", "/")) + assert.Equal(t, "/a/hola", joinPaths("/a", "/hola")) + assert.Equal(t, "/a/hola", joinPaths("/a/", "/hola")) + assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola/")) + assert.Equal(t, "/a/hola/", joinPaths("/a/", "/hola//")) } type bindTestStruct struct { @@ -113,8 +113,8 @@ func TestBindMiddleware(t *testing.T) { }) performRequest(router, "GET", "/?foo=hola&bar=10") assert.True(t, called) - assert.Equal(t, value.Foo, "hola") - assert.Equal(t, value.Bar, 10) + assert.Equal(t, "hola", value.Foo) + assert.Equal(t, 10, value.Bar) called = false performRequest(router, "GET", "/?foo=hola&bar=1") From 6f94fd05c98d1043e329398f6f742dc680be9b29 Mon Sep 17 00:00:00 2001 From: Boris Borshevsky Date: Wed, 29 Nov 2017 04:50:14 +0200 Subject: [PATCH 127/912] Linting and optimizing struct memory signature. (#1184) * fix cleanPath spell (#969) * linter and optimize structs --- README.md | 2 +- benchmarks_test.go | 2 -- binding/form_mapping.go | 9 --------- errors.go | 2 +- examples/custom-validation/server.go | 2 +- examples/realtime-advanced/stats.go | 4 ++-- gin.go | 21 +++++++++++---------- gin_integration_test.go | 2 +- mode.go | 6 +++--- tree.go | 14 +++++++------- tree_test.go | 11 ----------- 11 files changed, 27 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 7d3b919..3ac051a 100644 --- a/README.md +++ b/README.md @@ -540,7 +540,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - validator "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v8" ) type Booking struct { diff --git a/benchmarks_test.go b/benchmarks_test.go index a2c62ba..e797003 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/form_mapping.go b/binding/form_mapping.go index c968dc0..dd8c624 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -179,12 +179,3 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val value.Set(reflect.ValueOf(t)) return nil } - -// Don't pass in pointers to bind to. Can lead to bugs. See: -// https://github.com/codegangsta/martini-contrib/issues/40 -// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659 -func ensureNotPointer(obj interface{}) { - if reflect.TypeOf(obj).Kind() == reflect.Ptr { - panic("Pointers are not accepted as binding models") - } -} diff --git a/errors.go b/errors.go index 6f3c986..dbfccd8 100644 --- a/errors.go +++ b/errors.go @@ -148,7 +148,7 @@ func (a errorMsgs) String() string { } var buffer bytes.Buffer for i, msg := range a { - fmt.Fprintf(&buffer, "Error #%02d: %s\n", (i + 1), msg.Err) + fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err) if msg.Meta != nil { fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta) } diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go index 0b67ce1..31d449f 100644 --- a/examples/custom-validation/server.go +++ b/examples/custom-validation/server.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - validator "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v8" ) type Booking struct { diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go index 4bca3ae..4afedcb 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/gin.go b/gin.go index b59712b..edcb919 100644 --- a/gin.go +++ b/gin.go @@ -49,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. @@ -102,6 +92,17 @@ type Engine struct { // 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{} diff --git a/gin_integration_test.go b/gin_integration_test.go index f45dd6c..52f7884 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/mode.go b/mode.go index 394bebe..9c4f024 100644 --- a/mode.go +++ b/mode.go @@ -14,9 +14,9 @@ import ( const ENV_GIN_MODE = "GIN_MODE" const ( - DebugMode string = "debug" - ReleaseMode string = "release" - TestMode string = "test" + DebugMode = "debug" + ReleaseMode = "release" + TestMode = "test" ) const ( debugCode = iota diff --git a/tree.go b/tree.go index f67edd5..20e3704 100644 --- a/tree.go +++ b/tree.go @@ -87,13 +87,13 @@ const ( type node struct { path string - wildChild bool - nType nodeType - maxParams uint8 indices string children []*node handlers HandlersChain priority uint32 + nType nodeType + maxParams uint8 + wildChild bool } // increments priority of the given child and reorders if necessary. @@ -384,7 +384,7 @@ walk: // Outer loop for walking the tree // Nothing found. // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. - tsr = (path == "/" && n.handlers != nil) + tsr = path == "/" && n.handlers != nil return } @@ -424,7 +424,7 @@ walk: // Outer loop for walking the tree } // ... but we can't - tsr = (len(path) == end+1) + tsr = len(path) == end+1 return } @@ -435,7 +435,7 @@ walk: // Outer loop for walking the tree // No handle found. Check if a handle for this path + a // trailing slash exists for TSR recommendation n = n.children[0] - tsr = (n.path == "/" && n.handlers != nil) + tsr = n.path == "/" && n.handlers != nil } return @@ -530,7 +530,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa // Nothing found. We can recommend to redirect to the same URL // without a trailing slash if a leaf exists for that path - found = (fixTrailingSlash && path == "/" && n.handlers != nil) + found = fixTrailingSlash && path == "/" && n.handlers != nil return } diff --git a/tree_test.go b/tree_test.go index c0edd42..5bc2717 100644 --- a/tree_test.go +++ b/tree_test.go @@ -5,22 +5,11 @@ package gin import ( - "fmt" "reflect" "strings" "testing" ) -func printChildren(n *node, prefix string) { - fmt.Printf(" %02d:%02d %s%s[%d] %v %t %d \r\n", n.priority, n.maxParams, prefix, n.path, len(n.children), n.handlers, n.wildChild, n.nType) - for l := len(n.path); l > 0; l-- { - prefix += " " - } - for _, child := range n.children { - printChildren(child, prefix) - } -} - // Used as a workaround since we can't compare functions or their addressses var fakeHandlerValue string From 9e895470ddd40a45f28dbe4e96bfb5e893d54f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 29 Nov 2017 16:42:51 +0800 Subject: [PATCH 128/912] add deprecated test case (#1176) * add deprecated test case * swap assert param * remove --- context_test.go | 25 +++++++++++++++++++++++++ deprecated_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 deprecated_test.go diff --git a/context_test.go b/context_test.go index 932d9b1..9024cfc 100644 --- a/context_test.go +++ b/context_test.go @@ -676,6 +676,7 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { func TestContextRenderHTML(t *testing.T) { w := httptest.NewRecorder() c, router := CreateTestContext(w) + templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) @@ -686,6 +687,30 @@ func TestContextRenderHTML(t *testing.T) { assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +func TestContextRenderHTML2(t *testing.T) { + w := httptest.NewRecorder() + c, router := CreateTestContext(w) + + // print debug warning log when Engine.trees > 0 + router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) + assert.Len(t, router.trees, 1) + + var b bytes.Buffer + setup(&b) + defer teardown() + + templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) + router.SetHTMLTemplate(templ) + + assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String()) + + c.HTML(201, "t", H{"name": "alexandernyquist"}) + + assert.Equal(t, 201, w.Code) + assert.Equal(t, "Hello alexandernyquist", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + // Tests that no HTML is rendered if code is 204 func TestContextRenderNoContentHTML(t *testing.T) { w := httptest.NewRecorder() diff --git a/deprecated_test.go b/deprecated_test.go new file mode 100644 index 0000000..7a875fe --- /dev/null +++ b/deprecated_test.go @@ -0,0 +1,31 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin/binding" + "github.com/stretchr/testify/assert" +) + +func TestBindWith(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + + var obj struct { + Foo string `form:"foo"` + Bar string `form:"bar"` + } + assert.NoError(t, c.BindWith(&obj, binding.Form)) + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, 0, w.Body.Len()) +} From 13a40fcd2c2807e872768b725d1abe32ecd4712f Mon Sep 17 00:00:00 2001 From: Max Hilbrunner Date: Sat, 16 Dec 2017 17:52:07 +0100 Subject: [PATCH 129/912] README: Small update to clarify on Goroutines (#1199) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ac051a..046752f 100644 --- a/README.md +++ b/README.md @@ -1071,7 +1071,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() { From 25e7cd75ed51f2e3ef2315e836fe8100608a3c47 Mon Sep 17 00:00:00 2001 From: TaeJun Park Date: Sun, 17 Dec 2017 09:05:30 +0900 Subject: [PATCH 130/912] Update README.md (#1188) change path from '~/go/...' to '$GOPATH/...' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 046752f..3e1af55 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,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 From b70b8ab20c6f15732226fbd536e28f138629a046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 17 Dec 2017 13:02:33 +0800 Subject: [PATCH 131/912] use lower if the struct is private (#1185) --- auth.go | 12 ++++++------ auth_test.go | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/auth.go b/auth.go index 302a4fa..c214309 100644 --- a/auth.go +++ b/auth.go @@ -17,8 +17,8 @@ const AuthUserKey = "user" type Accounts map[string]string type authPair struct { - Value string - User string + value string + user string } type authPairs []authPair @@ -28,8 +28,8 @@ func (a authPairs) searchCredential(authValue string) (string, bool) { return "", false } for _, pair := range a { - if pair.Value == authValue { - return pair.User, true + if pair.value == authValue { + return pair.user, true } } return "", false @@ -74,8 +74,8 @@ func processAccounts(accounts Accounts) authPairs { 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 diff --git a/auth_test.go b/auth_test.go index 2f1ae70..dc8523b 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=", }) } From 46662e700bd20289503475770dbf0384e43398eb Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 20 Dec 2017 07:02:39 +0530 Subject: [PATCH 132/912] Doc: Fix typo in documentation of Bind (#1204) --- context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 1ed65fe..90d4c6e 100644 --- a/context.go +++ b/context.go @@ -449,10 +449,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error // Depending the "Content-Type" header different bindings are used: // "application/json" --> JSON binding // "application/xml" --> XML binding -// otherwise --> returns an error +// otherwise --> returns an error. // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. -// It will writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. +// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. func (c *Context) Bind(obj interface{}) error { b := binding.Default(c.Request.Method, c.ContentType()) return c.MustBindWith(obj, b) From 05547037e47317954d30d9858ef2015607483739 Mon Sep 17 00:00:00 2001 From: Levi Olson Date: Wed, 20 Dec 2017 20:48:11 -0600 Subject: [PATCH 133/912] Minor grammatical correction in README (#1206) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e1af55..6313788 100644 --- a/README.md +++ b/README.md @@ -385,7 +385,7 @@ func main() { r := gin.New() // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release. + // 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()) From a816f9e9dbf820d7d01e8d363539da976fb8aa40 Mon Sep 17 00:00:00 2001 From: Weilin Shi <934587911@qq.com> Date: Thu, 21 Dec 2017 11:00:17 +0800 Subject: [PATCH 134/912] Remove redundant if err != nil check (#1202) * Remove redundant if err != nil check * Return e.EncodeToken instead of create a new variable --- render/render_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/render/render_test.go b/render/render_test.go index e4df5b6..35662cf 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -111,10 +111,8 @@ func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return err } } - if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil { - return err - } - return nil + + return e.EncodeToken(xml.EndElement{Name: start.Name}) } func TestRenderXML(t *testing.T) { From 6626358d4fefa40ec37ca4b502c6c7d0cdea6d18 Mon Sep 17 00:00:00 2001 From: Weilin Shi <934587911@qq.com> Date: Mon, 25 Dec 2017 13:58:02 +0800 Subject: [PATCH 135/912] Fix golint warnings in utils.go (#1209) --- utils.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/utils.go b/utils.go index e909bb7..99d19af 100644 --- a/utils.go +++ b/utils.go @@ -33,18 +33,23 @@ func Bind(val interface{}) HandlerFunc { } } +// WrapF is a helper function for wrapping http.HandlerFunc +// Returns a Gin middleware func WrapF(f http.HandlerFunc) HandlerFunc { return func(c *Context) { f(c.Writer, c.Request) } } +// WrapH is a helper function for wrapping http.Handler +// Returns a Gin middleware func WrapH(h http.Handler) HandlerFunc { return func(c *Context) { h.ServeHTTP(c.Writer, c.Request) } } +// H is a shortcup for map[string]interface{} type H map[string]interface{} // MarshalXML allows type H to be used with xml.Marshal. @@ -65,10 +70,8 @@ func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return err } } - if err := e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil { - return err - } - return nil + + return e.EncodeToken(xml.EndElement{Name: start.Name}) } func assert1(guard bool, text string) { From a712f77d7aaec7eb4766a663924aaa4e54d3fe70 Mon Sep 17 00:00:00 2001 From: Weilin Shi <934587911@qq.com> Date: Fri, 29 Dec 2017 17:10:28 +0800 Subject: [PATCH 136/912] Fix some golint warnings in gin.go (#1215) --- gin.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gin.go b/gin.go index edcb919..4205eff 100644 --- a/gin.go +++ b/gin.go @@ -160,11 +160,14 @@ func (engine *Engine) Delims(left, right string) *Engine { 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 @@ -179,6 +182,8 @@ func (engine *Engine) LoadHTMLGlob(pattern string) { engine.SetHTMLTemplate(templ) } +// LoadHTMLFiles loads a slice of HTML files +// and associates the result with HTML renderer. func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} @@ -189,6 +194,7 @@ func (engine *Engine) LoadHTMLFiles(files ...string) { engine.SetHTMLTemplate(templ) } +// SetHTMLTemplate associate a template with HTML renderer. func (engine *Engine) SetHTMLTemplate(templ *template.Template) { if len(engine.trees) > 0 { debugPrintWARNINGSetHTMLTemplate() @@ -197,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 } From 8a6792d5162d449eb79743e372d5b651cc385561 Mon Sep 17 00:00:00 2001 From: Kevin Zhu Date: Tue, 23 Jan 2018 10:07:33 +0800 Subject: [PATCH 137/912] Fix README.md example code (#1231) r -> router --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6313788..2ad2fc1 100644 --- a/README.md +++ b/README.md @@ -435,7 +435,7 @@ func main() { c.String(200, "pong") }) - r.Run(":8080") +    router.Run(":8080") } ``` From 7a9a290b36bb803a18874aa5c5b108be2d7eb34d Mon Sep 17 00:00:00 2001 From: MW Lim Date: Tue, 23 Jan 2018 10:36:36 +0800 Subject: [PATCH 138/912] minor typo in README.md (#1219) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ad2fc1..6e421f0 100644 --- a/README.md +++ b/README.md @@ -1190,7 +1190,7 @@ func main() { ### Run multiple service using Gin -See the [question](https://github.com/gin-gonic/gin/issues/346) and try the folling example: +See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: [embedmd]:# (examples/multiple-service/main.go go) ```go From 2fbb97117cda046278ad34bd7bac35cbc38c68d5 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 24 Jan 2018 11:06:42 +0800 Subject: [PATCH 139/912] fix(build): remove unused target. (#1183) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9ba475a..51a2d19 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ 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 From 783c7ee9c14eac0e65b501664b4f553291556b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 26 Jan 2018 11:46:11 +0800 Subject: [PATCH 140/912] Add some test cases and run test cases on binding/render dir (#1168) * Travis run test cases on binding and render dir and add some test cases for binding and render --- .gitignore | 1 + Makefile | 2 +- binding/binding_test.go | 768 ++++++++++++++++++++++++++++++++++++++++ coverage.sh | 13 + render/render_test.go | 175 ++++++++- 5 files changed, 957 insertions(+), 2 deletions(-) create mode 100644 coverage.sh diff --git a/.gitignore b/.gitignore index f3b636d..14dc8f2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor/* !vendor/vendor.json coverage.out count.out +test diff --git a/Makefile b/Makefile index 51a2d19..5468563 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ install: deps .PHONY: test test: - go test -v -covermode=count -coverprofile=coverage.out + sh coverage.sh .PHONY: fmt fmt: diff --git a/binding/binding_test.go b/binding/binding_test.go index 5575e16..0c0f9f8 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -6,9 +6,13 @@ package binding import ( "bytes" + "encoding/json" + "errors" + "io/ioutil" "mime/multipart" "net/http" "testing" + "time" "github.com/gin-gonic/gin/binding/example" "github.com/golang/protobuf/proto" @@ -25,6 +29,116 @@ type FooBarStruct struct { Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } +type FooStructUseNumber struct { + Foo interface{} `json:"foo" binding:"required"` +} + +type FooBarStructForTimeType struct { + TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` + TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` +} + +type FooStructForTimeTypeNotFormat struct { + TimeFoo time.Time `form:"time_foo"` +} + +type FooStructForTimeTypeFailFormat struct { + TimeFoo time.Time `form:"time_foo" time_format:"2017-11-15"` +} + +type FooStructForTimeTypeFailLocation struct { + TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_location:"/asia/chongqing"` +} + +type FooStructForMapType struct { + // Unknown type: not support map + MapFoo map[string]interface{} `form:"map_foo"` +} + +type InvalidNameType struct { + TestName string `invalid_name:"test_name"` +} + +type InvalidNameMapType struct { + TestName struct { + MapFoo map[string]interface{} `form:"map_foo"` + } +} + +type FooStructForSliceType struct { + SliceFoo []int `form:"slice_foo"` +} + +type FooStructForSliceMapType struct { + // Unknown type: not support map + SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` +} + +type FooBarStructForIntType struct { + IntFoo int `form:"int_foo"` + IntBar int `form:"int_bar" binding:"required"` +} + +type FooBarStructForInt8Type struct { + Int8Foo int8 `form:"int8_foo"` + Int8Bar int8 `form:"int8_bar" binding:"required"` +} + +type FooBarStructForInt16Type struct { + Int16Foo int16 `form:"int16_foo"` + Int16Bar int16 `form:"int16_bar" binding:"required"` +} + +type FooBarStructForInt32Type struct { + Int32Foo int32 `form:"int32_foo"` + Int32Bar int32 `form:"int32_bar" binding:"required"` +} + +type FooBarStructForInt64Type struct { + Int64Foo int64 `form:"int64_foo"` + Int64Bar int64 `form:"int64_bar" binding:"required"` +} + +type FooBarStructForUintType struct { + UintFoo uint `form:"uint_foo"` + UintBar uint `form:"uint_bar" binding:"required"` +} + +type FooBarStructForUint8Type struct { + Uint8Foo uint8 `form:"uint8_foo"` + Uint8Bar uint8 `form:"uint8_bar" binding:"required"` +} + +type FooBarStructForUint16Type struct { + Uint16Foo uint16 `form:"uint16_foo"` + Uint16Bar uint16 `form:"uint16_bar" binding:"required"` +} + +type FooBarStructForUint32Type struct { + Uint32Foo uint32 `form:"uint32_foo"` + Uint32Bar uint32 `form:"uint32_bar" binding:"required"` +} + +type FooBarStructForUint64Type struct { + Uint64Foo uint64 `form:"uint64_foo"` + Uint64Bar uint64 `form:"uint64_bar" binding:"required"` +} + +type FooBarStructForBoolType struct { + BoolFoo bool `form:"bool_foo"` + BoolBar bool `form:"bool_bar" binding:"required"` +} + +type FooBarStructForFloat32Type struct { + Float32Foo float32 `form:"float32_foo"` + Float32Bar float32 `form:"float32_bar" binding:"required"` +} + +type FooBarStructForFloat64Type struct { + Float64Foo float64 `form:"float64_foo"` + Float64Bar float64 `form:"float64_bar" binding:"required"` +} + func TestBindingDefault(t *testing.T) { assert.Equal(t, Default("GET", ""), Form) assert.Equal(t, Default("GET", MIMEJSON), Form) @@ -55,6 +169,20 @@ func TestBindingJSON(t *testing.T) { `{"foo": "bar"}`, `{"bar": "foo"}`) } +func TestBindingJSONUseNumber(t *testing.T) { + testBodyBindingUseNumber(t, + JSON, "json", + "/", "/", + `{"foo": 123}`, `{"bar": "foo"}`) +} + +func TestBindingJSONUseNumber2(t *testing.T) { + testBodyBindingUseNumber2(t, + JSON, "json", + "/", "/", + `{"foo": 123}`, `{"bar": "foo"}`) +} + func TestBindingForm(t *testing.T) { testFormBinding(t, "POST", "/", "/", @@ -67,6 +195,174 @@ func TestBindingForm2(t *testing.T) { "", "") } +func TestBindingFormForTime(t *testing.T) { + testFormBindingForTime(t, "POST", + "/", "/", + "time_foo=2017-11-15&time_bar=", "bar2=foo") + testFormBindingForTimeNotFormat(t, "POST", + "/", "/", + "time_foo=2017-11-15", "bar2=foo") + testFormBindingForTimeFailFormat(t, "POST", + "/", "/", + "time_foo=2017-11-15", "bar2=foo") + testFormBindingForTimeFailLocation(t, "POST", + "/", "/", + "time_foo=2017-11-15", "bar2=foo") +} + +func TestBindingFormForTime2(t *testing.T) { + testFormBindingForTime(t, "GET", + "/?time_foo=2017-11-15&time_bar=", "/?bar2=foo", + "", "") + testFormBindingForTimeNotFormat(t, "GET", + "/?time_foo=2017-11-15", "/?bar2=foo", + "", "") + testFormBindingForTimeFailFormat(t, "GET", + "/?time_foo=2017-11-15", "/?bar2=foo", + "", "") + testFormBindingForTimeFailLocation(t, "GET", + "/?time_foo=2017-11-15", "/?bar2=foo", + "", "") +} + +func TestBindingFormInvalidName(t *testing.T) { + testFormBindingInvalidName(t, "POST", + "/", "/", + "test_name=bar", "bar2=foo") +} + +func TestBindingFormInvalidName2(t *testing.T) { + testFormBindingInvalidName2(t, "POST", + "/", "/", + "map_foo=bar", "bar2=foo") +} + +func TestBindingFormForType(t *testing.T) { + testFormBindingForType(t, "POST", + "/", "/", + "map_foo=", "bar2=1", "Map") + + testFormBindingForType(t, "POST", + "/", "/", + "slice_foo=1&slice_foo=2", "bar2=1&bar2=2", "Slice") + + testFormBindingForType(t, "GET", + "/?slice_foo=1&slice_foo=2", "/?bar2=1&bar2=2", + "", "", "Slice") + + testFormBindingForType(t, "POST", + "/", "/", + "slice_map_foo=1&slice_map_foo=2", "bar2=1&bar2=2", "SliceMap") + + testFormBindingForType(t, "GET", + "/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2", + "", "", "SliceMap") + + testFormBindingForType(t, "POST", + "/", "/", + "int_foo=&int_bar=-12", "bar2=-123", "Int") + + testFormBindingForType(t, "GET", + "/?int_foo=&int_bar=-12", "/?bar2=-123", + "", "", "Int") + + testFormBindingForType(t, "POST", + "/", "/", + "int8_foo=&int8_bar=-12", "bar2=-123", "Int8") + + testFormBindingForType(t, "GET", + "/?int8_foo=&int8_bar=-12", "/?bar2=-123", + "", "", "Int8") + + testFormBindingForType(t, "POST", + "/", "/", + "int16_foo=&int16_bar=-12", "bar2=-123", "Int16") + + testFormBindingForType(t, "GET", + "/?int16_foo=&int16_bar=-12", "/?bar2=-123", + "", "", "Int16") + + testFormBindingForType(t, "POST", + "/", "/", + "int32_foo=&int32_bar=-12", "bar2=-123", "Int32") + + testFormBindingForType(t, "GET", + "/?int32_foo=&int32_bar=-12", "/?bar2=-123", + "", "", "Int32") + + testFormBindingForType(t, "POST", + "/", "/", + "int64_foo=&int64_bar=-12", "bar2=-123", "Int64") + + testFormBindingForType(t, "GET", + "/?int64_foo=&int64_bar=-12", "/?bar2=-123", + "", "", "Int64") + + testFormBindingForType(t, "POST", + "/", "/", + "uint_foo=&uint_bar=12", "bar2=123", "Uint") + + testFormBindingForType(t, "GET", + "/?uint_foo=&uint_bar=12", "/?bar2=123", + "", "", "Uint") + + testFormBindingForType(t, "POST", + "/", "/", + "uint8_foo=&uint8_bar=12", "bar2=123", "Uint8") + + testFormBindingForType(t, "GET", + "/?uint8_foo=&uint8_bar=12", "/?bar2=123", + "", "", "Uint8") + + testFormBindingForType(t, "POST", + "/", "/", + "uint16_foo=&uint16_bar=12", "bar2=123", "Uint16") + + testFormBindingForType(t, "GET", + "/?uint16_foo=&uint16_bar=12", "/?bar2=123", + "", "", "Uint16") + + testFormBindingForType(t, "POST", + "/", "/", + "uint32_foo=&uint32_bar=12", "bar2=123", "Uint32") + + testFormBindingForType(t, "GET", + "/?uint32_foo=&uint32_bar=12", "/?bar2=123", + "", "", "Uint32") + + testFormBindingForType(t, "POST", + "/", "/", + "uint64_foo=&uint64_bar=12", "bar2=123", "Uint64") + + testFormBindingForType(t, "GET", + "/?uint64_foo=&uint64_bar=12", "/?bar2=123", + "", "", "Uint64") + + testFormBindingForType(t, "POST", + "/", "/", + "bool_foo=&bool_bar=true", "bar2=true", "Bool") + + testFormBindingForType(t, "GET", + "/?bool_foo=&bool_bar=true", "/?bar2=true", + "", "", "Bool") + + testFormBindingForType(t, "POST", + "/", "/", + "float32_foo=&float32_bar=-12.34", "bar2=12.3", "Float32") + + testFormBindingForType(t, "GET", + "/?float32_foo=&float32_bar=-12.34", "/?bar2=12.3", + "", "", "Float32") + + testFormBindingForType(t, "POST", + "/", "/", + "float64_foo=&float64_bar=-12.34", "bar2=12.3", "Float64") + + testFormBindingForType(t, "GET", + "/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3", + "", "", "Float64") +} + func TestBindingQuery(t *testing.T) { testQueryBinding(t, "POST", "/?foo=bar&bar=foo", "/", @@ -79,6 +375,18 @@ func TestBindingQuery2(t *testing.T) { "foo=unused", "") } +func TestBindingQueryFail(t *testing.T) { + testQueryBindingFail(t, "POST", + "/?map_foo=", "/", + "map_foo=unused", "bar2=foo") +} + +func TestBindingQueryFail2(t *testing.T) { + testQueryBindingFail(t, "GET", + "/?map_foo=", "/?bar2=foo", + "map_foo=unused", "") +} + func TestBindingXML(t *testing.T) { testBodyBinding(t, XML, "xml", @@ -86,12 +394,25 @@ func TestBindingXML(t *testing.T) { "bar", "foo") } +func TestBindingXMLFail(t *testing.T) { + testBodyBindingFail(t, + XML, "xml", + "/", "/", + "bar", "foo") +} + func createFormPostRequest() *http.Request { req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) req.Header.Set("Content-Type", MIMEPOSTForm) return req } +func createFormPostRequestFail() *http.Request { + req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar")) + req.Header.Set("Content-Type", MIMEPOSTForm) + return req +} + func createFormMultipartRequest() *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) @@ -106,24 +427,53 @@ func createFormMultipartRequest() *http.Request { return req } +func createFormMultipartRequestFail() *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + mw.SetBoundary(boundary) + mw.WriteField("map_foo", "bar") + req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + return req +} + func TestBindingFormPost(t *testing.T) { req := createFormPostRequest() var obj FooBarStruct FormPost.Bind(req, &obj) + assert.Equal(t, FormPost.Name(), "form-urlencoded") assert.Equal(t, obj.Foo, "bar") assert.Equal(t, obj.Bar, "foo") } +func TestBindingFormPostFail(t *testing.T) { + req := createFormPostRequestFail() + var obj FooStructForMapType + err := FormPost.Bind(req, &obj) + assert.Error(t, err) +} + func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest() var obj FooBarStruct FormMultipart.Bind(req, &obj) + assert.Equal(t, FormMultipart.Name(), "multipart/form-data") assert.Equal(t, obj.Foo, "bar") assert.Equal(t, obj.Bar, "foo") } +func TestBindingFormMultipartFail(t *testing.T) { + req := createFormMultipartRequestFail() + var obj FooStructForMapType + err := FormMultipart.Bind(req, &obj) + assert.Error(t, err) +} + func TestBindingProtoBuf(t *testing.T) { test := &example.Test{ Label: proto.String("yes"), @@ -136,6 +486,18 @@ func TestBindingProtoBuf(t *testing.T) { string(data), string(data[1:])) } +func TestBindingProtoBufFail(t *testing.T) { + test := &example.Test{ + Label: proto.String("yes"), + } + data, _ := proto.Marshal(test) + + testProtoBodyBindingFail(t, + ProtoBuf, "protobuf", + "/", "/", + string(data), string(data[1:])) +} + func TestBindingMsgPack(t *testing.T) { test := FooStruct{ Foo: "bar", @@ -216,6 +578,323 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) assert.Error(t, err) } +func TestFormBindingFail(t *testing.T) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooBarStruct{} + req, _ := http.NewRequest("POST", "/", nil) + err := b.Bind(req, &obj) + assert.Error(t, err) +} + +func TestFormPostBindingFail(t *testing.T) { + b := FormPost + assert.Equal(t, b.Name(), "form-urlencoded") + + obj := FooBarStruct{} + req, _ := http.NewRequest("POST", "/", nil) + err := b.Bind(req, &obj) + assert.Error(t, err) +} + +func TestFormMultipartBindingFail(t *testing.T) { + b := FormMultipart + assert.Equal(t, b.Name(), "multipart/form-data") + + obj := FooBarStruct{} + req, _ := http.NewRequest("POST", "/", nil) + err := b.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooBarStructForTimeType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + + assert.NoError(t, err) + assert.Equal(t, obj.TimeFoo.Unix(), int64(1510675200)) + assert.Equal(t, obj.TimeFoo.Location().String(), "Asia/Chongqing") + assert.Equal(t, obj.TimeBar.Unix(), int64(-62135596800)) + assert.Equal(t, obj.TimeBar.Location().String(), "UTC") + + obj = FooBarStructForTimeType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooStructForTimeTypeNotFormat{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = FooStructForTimeTypeNotFormat{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooStructForTimeTypeFailFormat{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = FooStructForTimeTypeFailFormat{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := FooStructForTimeTypeFailLocation{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = FooStructForTimeTypeFailLocation{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := InvalidNameType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.TestName, "") + + obj = InvalidNameType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, b.Name(), "form") + + obj := InvalidNameMapType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = InvalidNameMapType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) { + b := Form + assert.Equal(t, b.Name(), "form") + + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + switch typ { + case "Int": + obj := FooBarStructForIntType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.IntFoo, int(0)) + assert.Equal(t, obj.IntBar, int(-12)) + + obj = FooBarStructForIntType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Int8": + obj := FooBarStructForInt8Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Int8Foo, int8(0)) + assert.Equal(t, obj.Int8Bar, int8(-12)) + + obj = FooBarStructForInt8Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Int16": + obj := FooBarStructForInt16Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Int16Foo, int16(0)) + assert.Equal(t, obj.Int16Bar, int16(-12)) + + obj = FooBarStructForInt16Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Int32": + obj := FooBarStructForInt32Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Int32Foo, int32(0)) + assert.Equal(t, obj.Int32Bar, int32(-12)) + + obj = FooBarStructForInt32Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Int64": + obj := FooBarStructForInt64Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Int64Foo, int64(0)) + assert.Equal(t, obj.Int64Bar, int64(-12)) + + obj = FooBarStructForInt64Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint": + obj := FooBarStructForUintType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.UintFoo, uint(0x0)) + assert.Equal(t, obj.UintBar, uint(0xc)) + + obj = FooBarStructForUintType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint8": + obj := FooBarStructForUint8Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Uint8Foo, uint8(0x0)) + assert.Equal(t, obj.Uint8Bar, uint8(0xc)) + + obj = FooBarStructForUint8Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint16": + obj := FooBarStructForUint16Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Uint16Foo, uint16(0x0)) + assert.Equal(t, obj.Uint16Bar, uint16(0xc)) + + obj = FooBarStructForUint16Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint32": + obj := FooBarStructForUint32Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Uint32Foo, uint32(0x0)) + assert.Equal(t, obj.Uint32Bar, uint32(0xc)) + + obj = FooBarStructForUint32Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Uint64": + obj := FooBarStructForUint64Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Uint64Foo, uint64(0x0)) + assert.Equal(t, obj.Uint64Bar, uint64(0xc)) + + obj = FooBarStructForUint64Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Float32": + obj := FooBarStructForFloat32Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Float32Foo, float32(0.0)) + assert.Equal(t, obj.Float32Bar, float32(-12.34)) + + obj = FooBarStructForFloat32Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Float64": + obj := FooBarStructForFloat64Type{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.Float64Foo, float64(0.0)) + assert.Equal(t, obj.Float64Bar, float64(-12.34)) + + obj = FooBarStructForFloat64Type{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Bool": + obj := FooBarStructForBoolType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.BoolFoo, false) + assert.Equal(t, obj.BoolBar, true) + + obj = FooBarStructForBoolType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Slice": + obj := FooStructForSliceType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, obj.SliceFoo, []int{1, 2}) + + obj = FooStructForSliceType{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + case "Map": + obj := FooStructForMapType{} + err := b.Bind(req, &obj) + assert.Error(t, err) + case "SliceMap": + obj := FooStructForSliceMapType{} + err := b.Bind(req, &obj) + assert.Error(t, err) + } +} + func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Query assert.Equal(t, b.Name(), "query") @@ -231,6 +910,19 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) assert.Equal(t, obj.Bar, "foo") } +func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) { + b := Query + assert.Equal(t, b.Name(), "query") + + obj := FooStructForMapType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) +} + func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, b.Name(), name) @@ -246,6 +938,58 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody assert.Error(t, err) } +func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := FooStructUseNumber{} + req := requestWithBody("POST", path, body) + EnableDecoderUseNumber = true + err := b.Bind(req, &obj) + assert.NoError(t, err) + // we hope it is int64(123) + v, e := obj.Foo.(json.Number).Int64() + assert.NoError(t, e) + assert.Equal(t, v, int64(123)) + + obj = FooStructUseNumber{} + req = requestWithBody("POST", badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := FooStructUseNumber{} + req := requestWithBody("POST", path, body) + EnableDecoderUseNumber = false + err := b.Bind(req, &obj) + assert.NoError(t, err) + // it will return float64(123) if not use EnableDecoderUseNumber + // maybe it is not hoped + assert.Equal(t, obj.Foo, float64(123)) + + obj = FooStructUseNumber{} + req = requestWithBody("POST", badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + +func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := FooStruct{} + req := requestWithBody("POST", path, body) + err := b.Bind(req, &obj) + assert.Error(t, err) + assert.Equal(t, obj.Foo, "") + + obj = FooStruct{} + req = requestWithBody("POST", badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, b.Name(), name) @@ -263,6 +1007,30 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba assert.Error(t, err) } +type hook struct{} + +func (h hook) Read([]byte) (int, error) { + return 0, errors.New("error") +} + +func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, b.Name(), name) + + obj := example.Test{} + req := requestWithBody("POST", path, body) + + req.Body = ioutil.NopCloser(&hook{}) + req.Header.Add("Content-Type", MIMEPROTOBUF) + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = example.Test{} + req = requestWithBody("POST", badPath, badBody) + req.Header.Add("Content-Type", MIMEPROTOBUF) + err = ProtoBuf.Bind(req, &obj) + assert.Error(t, err) +} + func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, b.Name(), name) diff --git a/coverage.sh b/coverage.sh new file mode 100644 index 0000000..81437f9 --- /dev/null +++ b/coverage.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +echo "mode: count" > coverage.out + +for d in $(go list ./... | grep -E 'gin$|binding$|render$'); do + go test -v -covermode=count -coverprofile=profile.out $d + if [ -f profile.out ]; then + cat profile.out | grep -v "mode:" >> coverage.out + rm profile.out + fi +done diff --git a/render/render_test.go b/render/render_test.go index 35662cf..530e222 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -7,7 +7,9 @@ package render import ( "bytes" "encoding/xml" + "errors" "html/template" + "net/http" "net/http/httptest" "testing" @@ -24,6 +26,9 @@ func TestRenderMsgPack(t *testing.T) { "foo": "bar", } + (MsgPack{data}).WriteContentType(w) + assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") + err := (MsgPack{data}).Render(w) assert.NoError(t, err) @@ -45,6 +50,9 @@ func TestRenderJSON(t *testing.T) { "foo": "bar", } + (JSON{data}).WriteContentType(w) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) + err := (JSON{data}).Render(w) assert.NoError(t, err) @@ -52,6 +60,14 @@ func TestRenderJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } +func TestRenderJSONPanics(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + assert.Panics(t, func() { (JSON{data}).Render(w) }) +} + func TestRenderIndentedJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ @@ -66,12 +82,24 @@ func TestRenderIndentedJSON(t *testing.T) { assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8") } +func TestRenderIndentedJSONPanics(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + err := (IndentedJSON{data}).Render(w) + assert.Error(t, err) +} + func TestRenderSecureJSON(t *testing.T) { w1 := httptest.NewRecorder() data := map[string]interface{}{ "foo": "bar", } + (SecureJSON{"while(1);", data}).WriteContentType(w1) + assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) + err1 := (SecureJSON{"while(1);", data}).Render(w1) assert.NoError(t, err1) @@ -91,6 +119,15 @@ func TestRenderSecureJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type")) } +func TestRenderSecureJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + err := (SecureJSON{"while(1);", data}).Render(w) + assert.Error(t, err) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal @@ -115,12 +152,45 @@ func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return e.EncodeToken(xml.EndElement{Name: start.Name}) } +func TestRenderYAML(t *testing.T) { + w := httptest.NewRecorder() + data := ` +a : Easy! +b: + c: 2 + d: [3, 4] + ` + (YAML{data}).WriteContentType(w) + assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") + + err := (YAML{data}).Render(w) + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n") + assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") +} + +type fail struct{} + +// Hook MarshalYAML +func (ft *fail) MarshalYAML() (interface{}, error) { + return nil, errors.New("fail") +} + +func TestRenderYAMLFail(t *testing.T) { + w := httptest.NewRecorder() + err := (YAML{&fail{}}).Render(w) + assert.Error(t, err) +} + func TestRenderXML(t *testing.T) { w := httptest.NewRecorder() data := xmlmap{ "foo": "bar", } + (XML{data}).WriteContentType(w) + assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") + err := (XML{data}).Render(w) assert.NoError(t, err) @@ -129,7 +199,30 @@ func TestRenderXML(t *testing.T) { } func TestRenderRedirect(t *testing.T) { - // TODO + req, err := http.NewRequest("GET", "/test-redirect", nil) + assert.NoError(t, err) + + data1 := Redirect{ + Code: 301, + Request: req, + Location: "/new/location", + } + + w := httptest.NewRecorder() + err = data1.Render(w) + assert.NoError(t, err) + + data2 := Redirect{ + Code: 200, + Request: req, + Location: "/new/location", + } + + w = httptest.NewRecorder() + assert.Panics(t, func() { data2.Render(w) }) + + // only improve coverage + data2.WriteContentType(w) } func TestRenderData(t *testing.T) { @@ -149,6 +242,12 @@ func TestRenderData(t *testing.T) { func TestRenderString(t *testing.T) { w := httptest.NewRecorder() + (String{ + Format: "hello %s %d", + Data: []interface{}{}, + }).WriteContentType(w) + assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + err := (String{ Format: "hola %s %d", Data: []interface{}{"manu", 2}, @@ -159,6 +258,19 @@ func TestRenderString(t *testing.T) { assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") } +func TestRenderStringLenZero(t *testing.T) { + w := httptest.NewRecorder() + + err := (String{ + Format: "hola %s %d", + Data: []interface{}{}, + }).Render(w) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), "hola %s %d") + assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") +} + func TestRenderHTMLTemplate(t *testing.T) { w := httptest.NewRecorder() templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) @@ -174,3 +286,64 @@ func TestRenderHTMLTemplate(t *testing.T) { assert.Equal(t, w.Body.String(), "Hello alexandernyquist") assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") } + +func TestRenderHTMLTemplateEmptyName(t *testing.T) { + w := httptest.NewRecorder() + templ := template.Must(template.New("").Parse(`Hello {{.name}}`)) + + htmlRender := HTMLProduction{Template: templ} + instance := htmlRender.Instance("", map[string]interface{}{ + "name": "alexandernyquist", + }) + + err := instance.Render(w) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), "Hello alexandernyquist") + assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") +} + +func TestRenderHTMLDebugFiles(t *testing.T) { + w := httptest.NewRecorder() + htmlRender := HTMLDebug{Files: []string{"../fixtures/basic/hello.tmpl"}, + Glob: "", + Delims: Delims{Left: "{[{", Right: "}]}"}, + FuncMap: nil, + } + instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{ + "name": "thinkerou", + }) + + err := instance.Render(w) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), "

Hello thinkerou

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

Hello thinkerou

") + assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") +} + +func TestRenderHTMLDebugPanics(t *testing.T) { + htmlRender := HTMLDebug{Files: nil, + Glob: "", + Delims: Delims{"{{", "}}"}, + FuncMap: nil, + } + assert.Panics(t, func() { htmlRender.Instance("", nil) }) +} From ae22f0c87091eb75e08429082935ec48035320ac Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Thu, 22 Feb 2018 21:00:20 +0800 Subject: [PATCH 141/912] chore(travis): add 1.10 version (#1256) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ec12cad..63b5d11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ go: - 1.7.x - 1.8.x - 1.9.x + - 1.10.x - master git: From cbb1ee80b1c2a53c46872e43ed15c664917eb905 Mon Sep 17 00:00:00 2001 From: README Bot <35302948+codetriage-readme-bot@users.noreply.github.com> Date: Thu, 22 Feb 2018 07:28:50 -0600 Subject: [PATCH 142/912] Add CodeTriage badge to gin-gonic/gin (#1249) Adds a badge showing the number of people helping this repo on CodeTriage. [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) ## What is CodeTriage? CodeTriage is an Open Source app that is designed to make contributing to Open Source projects easier. It works by sending subscribers a few open issues in their inbox. If subscribers get busy, there is an algorithm that backs off issue load so they do not get overwhelmed [Read more about the CodeTriage project](https://www.codetriage.com/what). ## Why am I getting this PR? Your project was picked by the human, @schneems. They selected it from the projects submitted to https://www.codetriage.com and hand edited the PR. How did your project get added to [CodeTriage](https://www.codetriage.com/what)? Roughly 6 months ago, [dinsaw](https://github.com/dinsaw) added this project to CodeTriage in order to start contributing. Since then, 5 people have subscribed to help this repo. ## What does adding a badge accomplish? Adding a badge invites people to help contribute to your project. It also lets developers know that others are invested in the longterm success and maintainability of the project. You can see an example of a CodeTriage badge on these popular OSS READMEs: - [![](https://www.codetriage.com/rails/rails/badges/users.svg)](https://www.codetriage.com/rails/rails) https://github.com/rails/rails - [![](https://www.codetriage.com/crystal-lang/crystal/badges/users.svg)](https://www.codetriage.com/crystal-lang/crystal) https://github.com/crystal-lang/crystal ## Have a question or comment? While I am a bot, this PR was manually reviewed and monitored by a human - @schneems. My job is writing commit messages and handling PR logistics. If you have any questions, you can reply back to this PR and they will be answered by @schneems. If you do not want a badge right now, no worries, close the PR, you will not hear from me again. Thanks for making your project Open Source! Any feedback is greatly appreciated. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6e421f0..b51aa10 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. From 5d3f30cfc81f9109850fa9043a19783cbbde68a5 Mon Sep 17 00:00:00 2001 From: Mario Kostelac Date: Fri, 23 Feb 2018 01:09:33 +0000 Subject: [PATCH 143/912] Make "" mode being the same as debug mode (#1250) Not setting mode explicitly sets gin into debug mode, but it does not make it possible to retrieve gin mode as Debug since it's set to "". --- mode.go | 3 +++ mode_test.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/mode.go b/mode.go index 9c4f024..9df4e45 100644 --- a/mode.go +++ b/mode.go @@ -53,6 +53,9 @@ func SetMode(value string) { default: panic("gin mode unknown: " + value) } + if value == "" { + value = DebugMode + } modeName = value } diff --git a/mode_test.go b/mode_test.go index 7eaca82..cf27acd 100644 --- a/mode_test.go +++ b/mode_test.go @@ -21,6 +21,10 @@ func TestSetMode(t *testing.T) { assert.Equal(t, TestMode, Mode()) os.Unsetenv(ENV_GIN_MODE) + SetMode("") + assert.Equal(t, debugCode, ginMode) + assert.Equal(t, DebugMode, Mode()) + SetMode(DebugMode) assert.Equal(t, debugCode, ginMode) assert.Equal(t, DebugMode, Mode()) From 3e3f9bca81da5a4d2664d075012ae5aca9e70b9e Mon Sep 17 00:00:00 2001 From: Romain Beuque Date: Tue, 20 Mar 2018 07:05:24 +0100 Subject: [PATCH 144/912] doc(graceful-shutdown): failure to ListenAndServe should be a reason to exit (#1287) Signed-off-by: Romain Beuque --- README.md | 4 ++-- examples/graceful-shutdown/graceful-shutdown/server.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b51aa10..72ac99b 100644 --- a/README.md +++ b/README.md @@ -1324,8 +1324,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) } }() diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go index 6debe7f..af4f214 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) } }() From 65a65c2edd4e86f96634a553f07a25c38f2b1c75 Mon Sep 17 00:00:00 2001 From: hellojukay Date: Tue, 20 Mar 2018 14:42:51 +0800 Subject: [PATCH 145/912] add gin panic time log (#1270) * add gin pinic time log * Update recovery.go --- recovery.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 89b39fe..dcbeba7 100644 --- a/recovery.go +++ b/recovery.go @@ -12,6 +12,7 @@ import ( "log" "net/http/httputil" "runtime" + "time" ) var ( @@ -38,7 +39,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { if logger != nil { stack := stack(3) httprequest, _ := httputil.DumpRequest(c.Request, false) - logger.Printf("[Recovery] panic recovered:\n%s\n%s\n%s%s", string(httprequest), err, stack, reset) + logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) } c.AbortWithStatus(500) } @@ -107,3 +108,8 @@ func function(pc uintptr) []byte { name = bytes.Replace(name, centerDot, dot, -1) return name } + +func timeFormat(t time.Time) string { + var timeString = t.Format("2006/01/02 - 15:04:05") + return timeString +} From 6d913fc343cfac80d656db5be67a0ffb1323ba0d Mon Sep 17 00:00:00 2001 From: Suhas Karanth Date: Thu, 29 Mar 2018 12:03:07 +0530 Subject: [PATCH 146/912] fix(binding): Expose validator engine used by the default Validator (#1277) * fix(binding): Expose validator engine used by the default Validator - Add func ValidatorEngine for returning the underlying validator engine used in the default StructValidator implementation. - Remove the function RegisterValidation from the StructValidator interface which made it immpossible to use a StructValidator implementation without the validator.v8 library. - Update and rename test for registering validation Test{RegisterValidation => ValidatorEngine}. - Update readme and example for registering custom validation. - Add example for registering struct level validation. - Add documentation for the following binding funcs/types: - Binding interface - StructValidator interface - Validator instance - Binding implementations - Default func * fix(binding): Move validator engine getter inside interface * docs: rm date cmd from custom validation demo --- README.md | 13 +++-- binding/binding.go | 23 +++++--- binding/default_validator.go | 8 ++- binding/json.go | 3 ++ binding/validate_test.go | 9 ++-- examples/custom-validation/server.go | 6 ++- examples/struct-lvl-validations/README.md | 50 ++++++++++++++++++ examples/struct-lvl-validations/server.go | 64 +++++++++++++++++++++++ 8 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 examples/struct-lvl-validations/README.md create mode 100644 examples/struct-lvl-validations/server.go diff --git a/README.md b/README.md index 72ac99b..7dd7f73 100644 --- a/README.md +++ b/README.md @@ -564,7 +564,11 @@ func bookableDate( func main() { route := gin.Default() - binding.Validator.RegisterValidation("bookabledate", bookableDate) + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + route.GET("/bookable", getBookable) route.Run(":8085") } @@ -580,13 +584,16 @@ func getBookable(c *gin.Context) { ``` ```console -$ curl "localhost:8085/bookable?check_in=2017-08-16&check_out=2017-08-17" +$ 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=2017-08-15&check_out=2017-08-16" +$ 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). diff --git a/binding/binding.go b/binding/binding.go index dc32d53..646eb80 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -6,8 +6,6 @@ package binding import ( "net/http" - - "gopkg.in/go-playground/validator.v8" ) const ( @@ -23,11 +21,18 @@ 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 } +// 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. @@ -36,14 +41,18 @@ type StructValidator interface { // Otherwise nil must be returned. ValidateStruct(interface{}) error - // RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key - // NOTE: if the key already exists, the previous validation function will be replaced. - // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation - RegisterValidation(string, validator.Func) 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{} @@ -55,6 +64,8 @@ var ( 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/default_validator.go b/binding/default_validator.go index 6336bb6..c67aa8a 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -28,9 +28,13 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error { return nil } -func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error { +// Engine returns the underlying validator engine which powers the default +// Validator instance. This is useful if you want to register custom validations +// or struct level validations. See validator GoDoc for more info - +// https://godoc.org/gopkg.in/go-playground/validator.v8 +func (v *defaultValidator) Engine() interface{} { v.lazyinit() - return v.validate.RegisterValidation(key, fn) + return v.validate } func (v *defaultValidator) lazyinit() { diff --git a/binding/json.go b/binding/json.go index b7c856a..e928a8c 100644 --- a/binding/json.go +++ b/binding/json.go @@ -10,6 +10,9 @@ import ( "github.com/gin-gonic/gin/json" ) +// EnableDecoderUseNumber is used to call the UseNumber method on the JSON +// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an +// interface{} as a Number instead of as a float64. var EnableDecoderUseNumber = false type jsonBinding struct{} diff --git a/binding/validate_test.go b/binding/validate_test.go index 8ca7998..cb76063 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -214,11 +214,14 @@ func notOne( return false } -func TestRegisterValidation(t *testing.T) { +func TestValidatorEngine(t *testing.T) { // This validates that the function `notOne` matches // the expected function signature by `defaultValidator` // and by extension the validator library. - err := Validator.RegisterValidation("notone", notOne) + engine, ok := Validator.Engine().(*validator.Validate) + assert.True(t, ok) + + err := engine.RegisterValidation("notone", notOne) // Check that we can register custom validation without error assert.Nil(t, err) @@ -228,6 +231,6 @@ func TestRegisterValidation(t *testing.T) { // Check that we got back non-nil errs assert.NotNil(t, errs) - // Check that the error matches expactation + // Check that the error matches expectation assert.Error(t, errs, "", "", "notone") } diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go index 31d449f..dea0c30 100644 --- a/examples/custom-validation/server.go +++ b/examples/custom-validation/server.go @@ -30,7 +30,11 @@ func bookableDate( func main() { route := gin.Default() - binding.Validator.RegisterValidation("bookabledate", bookableDate) + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + route.GET("/bookable", getBookable) route.Run(":8085") } diff --git a/examples/struct-lvl-validations/README.md b/examples/struct-lvl-validations/README.md new file mode 100644 index 0000000..1bd57f0 --- /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 0000000..be807b7 --- /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(), + }) + } +} From 6ad7b9c9d382844036334ad061b832e56269e14f Mon Sep 17 00:00:00 2001 From: Yoshiyuki Kinjo Date: Tue, 17 Apr 2018 11:54:40 +0900 Subject: [PATCH 147/912] Fix documentation typo (#1321) --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 99d19af..278029d 100644 --- a/utils.go +++ b/utils.go @@ -49,7 +49,7 @@ func WrapH(h http.Handler) HandlerFunc { } } -// H is a shortcup for map[string]interface{} +// H is a shortcut for map[string]interface{} type H map[string]interface{} // MarshalXML allows type H to be used with xml.Marshal. From 3455d7f38877a931d0b97bc7c800e6963351ed2f Mon Sep 17 00:00:00 2001 From: esplo Date: Thu, 19 Apr 2018 13:00:22 +0900 Subject: [PATCH 148/912] change README for app-engine example because of goapp deprecation (#1324) the App Engine SDK is superseded by the Cloud SDK --- examples/app-engine/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/app-engine/README.md b/examples/app-engine/README.md index 48505de..b3dd7c7 100644 --- a/examples/app-engine/README.md +++ b/examples/app-engine/README.md @@ -1,7 +1,8 @@ # Guide to run Gin under App Engine LOCAL Development Server 1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.) -2. Download SDK for your platform from here: `https://developers.google.com/appengine/downloads?hl=es#Google_App_Engine_SDK_for_Go` +2. Download SDK for your platform from [here](https://cloud.google.com/appengine/docs/standard/go/download): `https://cloud.google.com/appengine/docs/standard/go/download` 3. Download Gin source code using: `$ go get github.com/gin-gonic/gin` -4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/` -5. Run it: `$ goapp serve app-engine/` \ No newline at end of file +4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/app-engine/` +5. Run it: `$ dev_appserver.py .` (notice that you have to run this script by Python2) + From 248c522e4a829f10cc525136d8e8f3d44f093ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 20 Apr 2018 09:54:00 +0800 Subject: [PATCH 149/912] Add Contents for README because it too long (#1325) --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 7dd7f73..38bde8d 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,47 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) +## 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) + - [Serving static files](#serving-static-files) + - [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) +- [Testing](#testing) +- [Users](#users--) + +## Quick start + ```sh # assume the following codes in example.go file $ cat example.go From dfe37ea6f1b9127be4cff4822a1308b4349444e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 20 Apr 2018 10:27:44 +0800 Subject: [PATCH 150/912] unify assert.Equal usage (#1327) * unify assert.Equal usage * fix typo --- binding/binding_test.go | 164 +++++++++++++++++++-------------------- binding/validate_test.go | 6 +- render/render_test.go | 50 ++++++------ 3 files changed, 110 insertions(+), 110 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 0c0f9f8..b239caf 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -140,26 +140,26 @@ type FooBarStructForFloat64Type struct { } func TestBindingDefault(t *testing.T) { - assert.Equal(t, Default("GET", ""), Form) - assert.Equal(t, Default("GET", MIMEJSON), Form) + assert.Equal(t, Form, Default("GET", "")) + assert.Equal(t, Form, Default("GET", MIMEJSON)) - assert.Equal(t, Default("POST", MIMEJSON), JSON) - assert.Equal(t, Default("PUT", MIMEJSON), JSON) + assert.Equal(t, JSON, Default("POST", MIMEJSON)) + assert.Equal(t, JSON, Default("PUT", MIMEJSON)) - assert.Equal(t, Default("POST", MIMEXML), XML) - assert.Equal(t, Default("PUT", MIMEXML2), XML) + assert.Equal(t, XML, Default("POST", MIMEXML)) + assert.Equal(t, XML, Default("PUT", MIMEXML2)) - assert.Equal(t, Default("POST", MIMEPOSTForm), Form) - assert.Equal(t, Default("PUT", MIMEPOSTForm), Form) + assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) + assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) - assert.Equal(t, Default("POST", MIMEMultipartPOSTForm), Form) - assert.Equal(t, Default("PUT", MIMEMultipartPOSTForm), Form) + assert.Equal(t, Form, Default("POST", MIMEMultipartPOSTForm)) + assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm)) - assert.Equal(t, Default("POST", MIMEPROTOBUF), ProtoBuf) - assert.Equal(t, Default("PUT", MIMEPROTOBUF), ProtoBuf) + assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) + assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) - assert.Equal(t, Default("POST", MIMEMSGPACK), MsgPack) - assert.Equal(t, Default("PUT", MIMEMSGPACK2), MsgPack) + assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) + assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) } func TestBindingJSON(t *testing.T) { @@ -445,9 +445,9 @@ func TestBindingFormPost(t *testing.T) { var obj FooBarStruct FormPost.Bind(req, &obj) - assert.Equal(t, FormPost.Name(), "form-urlencoded") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "form-urlencoded", FormPost.Name()) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) } func TestBindingFormPostFail(t *testing.T) { @@ -462,9 +462,9 @@ func TestBindingFormMultipart(t *testing.T) { var obj FooBarStruct FormMultipart.Bind(req, &obj) - assert.Equal(t, FormMultipart.Name(), "multipart/form-data") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "multipart/form-data", FormMultipart.Name()) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) } func TestBindingFormMultipartFail(t *testing.T) { @@ -560,7 +560,7 @@ func TestExistsFails(t *testing.T) { func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} req := requestWithBody(method, path, body) @@ -569,8 +569,8 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) } err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) obj = FooBarStruct{} req = requestWithBody(method, badPath, badBody) @@ -580,7 +580,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) func TestFormBindingFail(t *testing.T) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) @@ -590,7 +590,7 @@ func TestFormBindingFail(t *testing.T) { func TestFormPostBindingFail(t *testing.T) { b := FormPost - assert.Equal(t, b.Name(), "form-urlencoded") + assert.Equal(t, "form-urlencoded", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) @@ -600,7 +600,7 @@ func TestFormPostBindingFail(t *testing.T) { func TestFormMultipartBindingFail(t *testing.T) { b := FormMultipart - assert.Equal(t, b.Name(), "multipart/form-data") + assert.Equal(t, "multipart/form-data", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) @@ -610,7 +610,7 @@ func TestFormMultipartBindingFail(t *testing.T) { func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooBarStructForTimeType{} req := requestWithBody(method, path, body) @@ -620,10 +620,10 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.TimeFoo.Unix(), int64(1510675200)) - assert.Equal(t, obj.TimeFoo.Location().String(), "Asia/Chongqing") - assert.Equal(t, obj.TimeBar.Unix(), int64(-62135596800)) - assert.Equal(t, obj.TimeBar.Location().String(), "UTC") + assert.Equal(t, int64(1510675200), obj.TimeFoo.Unix()) + assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String()) + assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix()) + assert.Equal(t, "UTC", obj.TimeBar.Location().String()) obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) @@ -633,7 +633,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeNotFormat{} req := requestWithBody(method, path, body) @@ -651,7 +651,7 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeFailFormat{} req := requestWithBody(method, path, body) @@ -669,7 +669,7 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeFailLocation{} req := requestWithBody(method, path, body) @@ -687,7 +687,7 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := InvalidNameType{} req := requestWithBody(method, path, body) @@ -696,7 +696,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo } err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.TestName, "") + assert.Equal(t, "", obj.TestName) obj = InvalidNameType{} req = requestWithBody(method, badPath, badBody) @@ -706,7 +706,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := InvalidNameMapType{} req := requestWithBody(method, path, body) @@ -724,7 +724,7 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) req := requestWithBody(method, path, body) if method == "POST" { @@ -735,8 +735,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForIntType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.IntFoo, int(0)) - assert.Equal(t, obj.IntBar, int(-12)) + assert.Equal(t, int(0), obj.IntFoo) + assert.Equal(t, int(-12), obj.IntBar) obj = FooBarStructForIntType{} req = requestWithBody(method, badPath, badBody) @@ -746,8 +746,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt8Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int8Foo, int8(0)) - assert.Equal(t, obj.Int8Bar, int8(-12)) + assert.Equal(t, int8(0), obj.Int8Foo) + assert.Equal(t, int8(-12), obj.Int8Bar) obj = FooBarStructForInt8Type{} req = requestWithBody(method, badPath, badBody) @@ -757,8 +757,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt16Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int16Foo, int16(0)) - assert.Equal(t, obj.Int16Bar, int16(-12)) + assert.Equal(t, int16(0), obj.Int16Foo) + assert.Equal(t, int16(-12), obj.Int16Bar) obj = FooBarStructForInt16Type{} req = requestWithBody(method, badPath, badBody) @@ -768,8 +768,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int32Foo, int32(0)) - assert.Equal(t, obj.Int32Bar, int32(-12)) + assert.Equal(t, int32(0), obj.Int32Foo) + assert.Equal(t, int32(-12), obj.Int32Bar) obj = FooBarStructForInt32Type{} req = requestWithBody(method, badPath, badBody) @@ -779,8 +779,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int64Foo, int64(0)) - assert.Equal(t, obj.Int64Bar, int64(-12)) + assert.Equal(t, int64(0), obj.Int64Foo) + assert.Equal(t, int64(-12), obj.Int64Bar) obj = FooBarStructForInt64Type{} req = requestWithBody(method, badPath, badBody) @@ -790,8 +790,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUintType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.UintFoo, uint(0x0)) - assert.Equal(t, obj.UintBar, uint(0xc)) + assert.Equal(t, uint(0x0), obj.UintFoo) + assert.Equal(t, uint(0xc), obj.UintBar) obj = FooBarStructForUintType{} req = requestWithBody(method, badPath, badBody) @@ -801,8 +801,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint8Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint8Foo, uint8(0x0)) - assert.Equal(t, obj.Uint8Bar, uint8(0xc)) + assert.Equal(t, uint8(0x0), obj.Uint8Foo) + assert.Equal(t, uint8(0xc), obj.Uint8Bar) obj = FooBarStructForUint8Type{} req = requestWithBody(method, badPath, badBody) @@ -812,8 +812,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint16Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint16Foo, uint16(0x0)) - assert.Equal(t, obj.Uint16Bar, uint16(0xc)) + assert.Equal(t, uint16(0x0), obj.Uint16Foo) + assert.Equal(t, uint16(0xc), obj.Uint16Bar) obj = FooBarStructForUint16Type{} req = requestWithBody(method, badPath, badBody) @@ -823,8 +823,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint32Foo, uint32(0x0)) - assert.Equal(t, obj.Uint32Bar, uint32(0xc)) + assert.Equal(t, uint32(0x0), obj.Uint32Foo) + assert.Equal(t, uint32(0xc), obj.Uint32Bar) obj = FooBarStructForUint32Type{} req = requestWithBody(method, badPath, badBody) @@ -834,8 +834,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint64Foo, uint64(0x0)) - assert.Equal(t, obj.Uint64Bar, uint64(0xc)) + assert.Equal(t, uint64(0x0), obj.Uint64Foo) + assert.Equal(t, uint64(0xc), obj.Uint64Bar) obj = FooBarStructForUint64Type{} req = requestWithBody(method, badPath, badBody) @@ -845,8 +845,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForFloat32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Float32Foo, float32(0.0)) - assert.Equal(t, obj.Float32Bar, float32(-12.34)) + assert.Equal(t, float32(0.0), obj.Float32Foo) + assert.Equal(t, float32(-12.34), obj.Float32Bar) obj = FooBarStructForFloat32Type{} req = requestWithBody(method, badPath, badBody) @@ -856,8 +856,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForFloat64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Float64Foo, float64(0.0)) - assert.Equal(t, obj.Float64Bar, float64(-12.34)) + assert.Equal(t, float64(0.0), obj.Float64Foo) + assert.Equal(t, float64(-12.34), obj.Float64Bar) obj = FooBarStructForFloat64Type{} req = requestWithBody(method, badPath, badBody) @@ -867,8 +867,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForBoolType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.BoolFoo, false) - assert.Equal(t, obj.BoolBar, true) + assert.False(t, obj.BoolFoo) + assert.True(t, obj.BoolBar) obj = FooBarStructForBoolType{} req = requestWithBody(method, badPath, badBody) @@ -878,7 +878,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooStructForSliceType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.SliceFoo, []int{1, 2}) + assert.Equal(t, []int{1, 2}, obj.SliceFoo) obj = FooStructForSliceType{} req = requestWithBody(method, badPath, badBody) @@ -897,7 +897,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Query - assert.Equal(t, b.Name(), "query") + assert.Equal(t, "query", b.Name()) obj := FooBarStruct{} req := requestWithBody(method, path, body) @@ -906,13 +906,13 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) } err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) } func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) { b := Query - assert.Equal(t, b.Name(), "query") + assert.Equal(t, "query", b.Name()) obj := FooStructForMapType{} req := requestWithBody(method, path, body) @@ -924,13 +924,13 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str } func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) @@ -939,7 +939,7 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody } func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) @@ -949,7 +949,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body // we hope it is int64(123) v, e := obj.Foo.(json.Number).Int64() assert.NoError(t, e) - assert.Equal(t, v, int64(123)) + assert.Equal(t, int64(123), v) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) @@ -958,7 +958,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body } func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) @@ -967,7 +967,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod assert.NoError(t, err) // it will return float64(123) if not use EnableDecoderUseNumber // maybe it is not hoped - assert.Equal(t, obj.Foo, float64(123)) + assert.Equal(t, float64(123), obj.Foo) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) @@ -976,13 +976,13 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod } func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) assert.Error(t, err) - assert.Equal(t, obj.Foo, "") + assert.Equal(t, "", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) @@ -991,14 +991,14 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad } func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := example.Test{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, *obj.Label, "yes") + assert.Equal(t, "yes", *obj.Label) obj = example.Test{} req = requestWithBody("POST", badPath, badBody) @@ -1014,7 +1014,7 @@ func (h hook) Read([]byte) (int, error) { } func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := example.Test{} req := requestWithBody("POST", path, body) @@ -1032,14 +1032,14 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body } func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEMSGPACK) err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) diff --git a/binding/validate_test.go b/binding/validate_test.go index cb76063..2c76b6d 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -176,7 +176,7 @@ func TestValidatePrimitives(t *testing.T) { obj := Object{"foo": "bar", "bar": 1} assert.NoError(t, validate(obj)) assert.NoError(t, validate(&obj)) - assert.Equal(t, obj, Object{"foo": "bar", "bar": 1}) + assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj) obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}} assert.NoError(t, validate(obj2)) @@ -185,12 +185,12 @@ func TestValidatePrimitives(t *testing.T) { nu := 10 assert.NoError(t, validate(nu)) assert.NoError(t, validate(&nu)) - assert.Equal(t, nu, 10) + assert.Equal(t, 10, nu) str := "value" assert.NoError(t, validate(str)) assert.NoError(t, validate(&str)) - assert.Equal(t, str, "value") + assert.Equal(t, "value", str) } // structCustomValidation is a helper struct we use to check that diff --git a/render/render_test.go b/render/render_test.go index 530e222..d825d04 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -27,7 +27,7 @@ func TestRenderMsgPack(t *testing.T) { } (MsgPack{data}).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) err := (MsgPack{data}).Render(w) @@ -41,7 +41,7 @@ func TestRenderMsgPack(t *testing.T) { assert.NoError(t, err) assert.Equal(t, w.Body.String(), string(buf.Bytes())) - assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderJSON(t *testing.T) { @@ -78,8 +78,8 @@ func TestRenderIndentedJSON(t *testing.T) { err := (IndentedJSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}") - assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8") + assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderIndentedJSONPanics(t *testing.T) { @@ -161,12 +161,12 @@ b: d: [3, 4] ` (YAML{data}).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") + assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) err := (YAML{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n") - assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") + assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String()) + assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } type fail struct{} @@ -189,13 +189,13 @@ func TestRenderXML(t *testing.T) { } (XML{data}).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) err := (XML{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "bar") - assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") + assert.Equal(t, "bar", w.Body.String()) + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderRedirect(t *testing.T) { @@ -235,8 +235,8 @@ func TestRenderData(t *testing.T) { }).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "#!PNG some raw data") - assert.Equal(t, w.Header().Get("Content-Type"), "image/png") + assert.Equal(t, "#!PNG some raw data", w.Body.String()) + assert.Equal(t, "image/png", w.Header().Get("Content-Type")) } func TestRenderString(t *testing.T) { @@ -246,7 +246,7 @@ func TestRenderString(t *testing.T) { Format: "hello %s %d", Data: []interface{}{}, }).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) err := (String{ Format: "hola %s %d", @@ -254,8 +254,8 @@ func TestRenderString(t *testing.T) { }).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "hola manu 2") - assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "hola manu 2", w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderStringLenZero(t *testing.T) { @@ -267,8 +267,8 @@ func TestRenderStringLenZero(t *testing.T) { }).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "hola %s %d") - assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "hola %s %d", w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLTemplate(t *testing.T) { @@ -283,8 +283,8 @@ func TestRenderHTMLTemplate(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "Hello alexandernyquist") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "Hello alexandernyquist", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLTemplateEmptyName(t *testing.T) { @@ -299,8 +299,8 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "Hello alexandernyquist") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "Hello alexandernyquist", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugFiles(t *testing.T) { @@ -317,8 +317,8 @@ func TestRenderHTMLDebugFiles(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "

Hello thinkerou

") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "

Hello thinkerou

", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugGlob(t *testing.T) { @@ -335,8 +335,8 @@ func TestRenderHTMLDebugGlob(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "

Hello thinkerou

") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "

Hello thinkerou

", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugPanics(t *testing.T) { From 814ac9490a38bb955a742d8ea15fa140489ee658 Mon Sep 17 00:00:00 2001 From: JINNOUCHI Yasushi Date: Sun, 22 Apr 2018 16:04:38 +0900 Subject: [PATCH 151/912] Add example to build single binary with templates (#1328) --- .travis.yml | 2 +- README.md | 45 +++++++++++++++++++++ examples/assets-in-binary/README.md | 33 ++++++++++++++++ examples/assets-in-binary/assets.go | 34 ++++++++++++++++ examples/assets-in-binary/html/bar.tmpl | 4 ++ examples/assets-in-binary/html/index.tmpl | 4 ++ examples/assets-in-binary/main.go | 48 +++++++++++++++++++++++ vendor/vendor.json | 6 +++ 8 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 examples/assets-in-binary/README.md create mode 100644 examples/assets-in-binary/assets.go create mode 100644 examples/assets-in-binary/html/bar.tmpl create mode 100644 examples/assets-in-binary/html/index.tmpl create mode 100644 examples/assets-in-binary/main.go diff --git a/.travis.yml b/.travis.yml index 63b5d11..e910156 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ go: - master git: - depth: 3 + depth: 10 install: - make install diff --git a/README.md b/README.md index 38bde8d..6c8988e 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [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) - [Testing](#testing) - [Users](#users--) @@ -1393,6 +1394,50 @@ func main() { } ``` +### 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. + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/examples/assets-in-binary/README.md b/examples/assets-in-binary/README.md new file mode 100644 index 0000000..0c23bb0 --- /dev/null +++ b/examples/assets-in-binary/README.md @@ -0,0 +1,33 @@ +# Building a single binary containing templates + +This is a complete example to create a single binary with the +[gin-gonic/gin][gin] Web Server with HTML templates. + +[gin]: https://github.com/gin-gonic/gin + +## How to use + +### Prepare Packages + +``` +go get github.com/gin-gonic/gin +go get github.com/jessevdk/go-assets-builder +``` + +### Generate assets.go + +``` +go-assets-builder html -o assets.go +``` + +### Build the server + +``` +go build -o assets-in-binary +``` + +### Run + +``` +./assets-in-binary +``` diff --git a/examples/assets-in-binary/assets.go b/examples/assets-in-binary/assets.go new file mode 100644 index 0000000..dcc5c46 --- /dev/null +++ b/examples/assets-in-binary/assets.go @@ -0,0 +1,34 @@ +package main + +import ( + "time" + + "github.com/jessevdk/go-assets" +) + +var _Assetsbfa8d115ce0617d89507412d5393a462f8e9b003 = "\n\n

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

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

Hello, {{.Foo}}

\n\n" + +// Assets returns go-assets FileSystem +var Assets = assets.NewFileSystem(map[string][]string{"/": {"html"}, "/html": {"bar.tmpl", "index.tmpl"}}, map[string]*assets.File{ + "/": { + Path: "/", + FileMode: 0x800001ed, + Mtime: time.Unix(1524365738, 1524365738517125470), + Data: nil, + }, "/html": { + Path: "/html", + FileMode: 0x800001ed, + Mtime: time.Unix(1524365491, 1524365491289799093), + Data: nil, + }, "/html/bar.tmpl": { + Path: "/html/bar.tmpl", + FileMode: 0x1a4, + Mtime: time.Unix(1524365491, 1524365491289611557), + Data: []byte(_Assetsbfa8d115ce0617d89507412d5393a462f8e9b003), + }, "/html/index.tmpl": { + Path: "/html/index.tmpl", + FileMode: 0x1a4, + Mtime: time.Unix(1524365491, 1524365491289995821), + Data: []byte(_Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2), + }}, "") diff --git a/examples/assets-in-binary/html/bar.tmpl b/examples/assets-in-binary/html/bar.tmpl new file mode 100644 index 0000000..c8e1c0f --- /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 0000000..6904fd5 --- /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 0000000..27bc3b1 --- /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/vendor/vendor.json b/vendor/vendor.json index 573abe2..5ace49d 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -33,6 +33,12 @@ "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revisionTime": "2017-06-01T23:02:30Z" }, + { + "checksumSHA1": "Cq9h7eDNXXyR/qJPvO8/Rk4pmFg=", + "path": "github.com/jessevdk/go-assets", + "revision": "4f4301a06e153ff90e17793577ab6bf79f8dc5c5", + "revisionTime": "2016-09-21T14:41:39Z" + }, { "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "path": "github.com/json-iterator/go", From 41f951e0cd126aa45a5cae1dc5c0d26f7cf80561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 25 Apr 2018 16:24:03 +0800 Subject: [PATCH 152/912] support default value for form (#1138) * support default value for form * fix bug for nil interface * use SplitN and optimization code * add test case * add test cases for form(own default value) * fix invalid code * fix code indent * assert order --- binding/binding_test.go | 52 +++++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 17 +++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index b239caf..ac82641 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -29,6 +29,11 @@ type FooBarStruct struct { Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } +type FooDefaultBarStruct struct { + FooStruct + Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"` +} + type FooStructUseNumber struct { Foo interface{} `json:"foo" binding:"required"` } @@ -195,6 +200,18 @@ func TestBindingForm2(t *testing.T) { "", "") } +func TestBindingFormDefaultValue(t *testing.T) { + testFormBindingDefaultValue(t, "POST", + "/", "/", + "foo=bar", "bar2=foo") +} + +func TestBindingFormDefaultValue2(t *testing.T) { + testFormBindingDefaultValue(t, "GET", + "/?foo=bar", "/?bar2=foo", + "", "") +} + func TestBindingFormForTime(t *testing.T) { testFormBindingForTime(t, "POST", "/", "/", @@ -407,6 +424,12 @@ func createFormPostRequest() *http.Request { return req } +func createDefaultFormPostRequest() *http.Request { + req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) + req.Header.Set("Content-Type", MIMEPOSTForm) + return req +} + func createFormPostRequestFail() *http.Request { req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar")) req.Header.Set("Content-Type", MIMEPOSTForm) @@ -450,6 +473,15 @@ func TestBindingFormPost(t *testing.T) { assert.Equal(t, "foo", obj.Bar) } +func TestBindingDefaultValueFormPost(t *testing.T) { + req := createDefaultFormPostRequest() + var obj FooDefaultBarStruct + FormPost.Bind(req, &obj) + + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "hello", obj.Bar) +} + func TestBindingFormPostFail(t *testing.T) { req := createFormPostRequestFail() var obj FooStructForMapType @@ -578,6 +610,26 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) assert.Error(t, err) } +func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, "form", b.Name()) + + obj := FooDefaultBarStruct{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "hello", obj.Bar) + + obj = FooDefaultBarStruct{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + func TestFormBindingFail(t *testing.T) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index dd8c624..7c680d9 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -8,6 +8,7 @@ import ( "errors" "reflect" "strconv" + "strings" "time" ) @@ -23,6 +24,15 @@ func mapForm(ptr interface{}, form map[string][]string) error { structFieldKind := structField.Kind() inputFieldName := typeField.Tag.Get("form") + inputFieldNameList := strings.Split(inputFieldName, ",") + inputFieldName = inputFieldNameList[0] + var defaultValue string + if len(inputFieldNameList) > 1 { + defaultList := strings.SplitN(inputFieldNameList[1], "=", 2) + if defaultList[0] == "default" { + defaultValue = defaultList[1] + } + } if inputFieldName == "" { inputFieldName = typeField.Name @@ -38,8 +48,13 @@ func mapForm(ptr interface{}, form map[string][]string) error { } } inputValue, exists := form[inputFieldName] + if !exists { - continue + if defaultValue == "" { + continue + } + inputValue = make([]string, 1) + inputValue[0] = defaultValue } numElems := len(inputValue) From 8c2401829041c89ddd6d89a0820acdd19a53855e Mon Sep 17 00:00:00 2001 From: senhtry Date: Thu, 26 Apr 2018 11:52:19 +0800 Subject: [PATCH 153/912] Add Jsonp Support to Context (#1333) --- README.md | 23 +++++++++++++++++++++++ context.go | 7 +++++++ context_test.go | 14 ++++++++++++++ render/json.go | 32 ++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 37 +++++++++++++++++++++++++++++++++++++ 6 files changed, 114 insertions(+) mode change 100644 => 100755 context.go mode change 100644 => 100755 render/json.go mode change 100644 => 100755 render/render.go mode change 100644 => 100755 render/render_test.go diff --git a/README.md b/README.md index 6c8988e..aba9ef3 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [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) - [HTML rendering](#html-rendering) - [Multitemplate](#multitemplate) @@ -861,6 +862,28 @@ func main() { 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 diff --git a/context.go b/context.go old mode 100644 new mode 100755 index 90d4c6e..be205f4 --- a/context.go +++ b/context.go @@ -670,6 +670,13 @@ func (c *Context) SecureJSON(code int, obj interface{}) { c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj}) } +// JSONP serializes the given struct as JSON into the response body. +// It add padding to response body to request data from a server residing in a different domain than the client. +// It also sets the Content-Type as "application/javascript". +func (c *Context) JSONP(code int, obj interface{}) { + c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj}) +} + // JSON serializes the given struct as JSON into the response body. // It also sets the Content-Type as "application/json". func (c *Context) JSON(code int, obj interface{}) { diff --git a/context_test.go b/context_test.go index 9024cfc..841d8af 100644 --- a/context_test.go +++ b/context_test.go @@ -581,6 +581,20 @@ func TestContextRenderJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// Tests that the response is serialized as JSONP +// and Content-Type is set to application/javascript +func TestContextRenderJSONP(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) + + c.JSONP(201, H{"foo": "bar"}) + + assert.Equal(t, 201, w.Code) + assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + // Tests that no JSON is rendered if code is 204 func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() diff --git a/render/json.go b/render/json.go old mode 100644 new mode 100755 index eb2548e..3a2e8b2 --- a/render/json.go +++ b/render/json.go @@ -6,6 +6,7 @@ package render import ( "bytes" + "html/template" "net/http" "github.com/gin-gonic/gin/json" @@ -24,9 +25,15 @@ type SecureJSON struct { Data interface{} } +type JsonpJSON struct { + Callback string + Data interface{} +} + type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} +var jsonpContentType = []string{"application/javascript; charset=utf-8"} func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { @@ -80,3 +87,28 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { func (r SecureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } + +func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + ret, err := json.Marshal(r.Data) + if err != nil { + return err + } + + if r.Callback == "" { + w.Write(ret) + return nil + } + + callback := template.JSEscapeString(r.Callback) + w.Write([]byte(callback)) + w.Write([]byte("(")) + w.Write(ret) + w.Write([]byte(")")) + + return nil +} + +func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonpContentType) +} diff --git a/render/render.go b/render/render.go old mode 100644 new mode 100755 index 7185236..578f278 --- a/render/render.go +++ b/render/render.go @@ -15,6 +15,7 @@ var ( _ Render = JSON{} _ Render = IndentedJSON{} _ Render = SecureJSON{} + _ Render = JsonpJSON{} _ Render = XML{} _ Render = String{} _ Render = Redirect{} diff --git a/render/render_test.go b/render/render_test.go old mode 100644 new mode 100755 index d825d04..c7b51ef --- a/render/render_test.go +++ b/render/render_test.go @@ -128,6 +128,43 @@ func TestRenderSecureJSONFail(t *testing.T) { assert.Error(t, err) } +func TestRenderJsonpJSON(t *testing.T) { + w1 := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + + (JsonpJSON{"x", data}).WriteContentType(w1) + assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) + + err1 := (JsonpJSON{"x", data}).Render(w1) + + assert.NoError(t, err1) + assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) + + w2 := httptest.NewRecorder() + datas := []map[string]interface{}{{ + "foo": "bar", + }, { + "bar": "foo", + }} + + err2 := (JsonpJSON{"x", datas}).Render(w2) + assert.NoError(t, err2) + assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) +} + +func TestRenderJsonpJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + err := (JsonpJSON{"x", data}).Render(w) + assert.Error(t, err) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal From 2282be059be78f9f7f70a72ca4dd3bcfe5f972ee Mon Sep 17 00:00:00 2001 From: Alexander Lokhman Date: Thu, 26 Apr 2018 15:09:34 +0100 Subject: [PATCH 154/912] Add support of pointers in form binding (#1336) * Add support of pointers in form binding * Add tests for pointer form binding --- binding/binding_test.go | 38 ++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 6 ++++++ 2 files changed, 44 insertions(+) diff --git a/binding/binding_test.go b/binding/binding_test.go index ac82641..e29e6df 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -144,6 +144,15 @@ type FooBarStructForFloat64Type struct { Float64Bar float64 `form:"float64_bar" binding:"required"` } +type FooStructForStringPtrType struct { + PtrFoo *string `form:"ptr_foo"` + PtrBar *string `form:"ptr_bar" binding:"required"` +} + +type FooStructForMapPtrType struct { + PtrBar *map[string]interface{} `form:"ptr_bar"` +} + func TestBindingDefault(t *testing.T) { assert.Equal(t, Form, Default("GET", "")) assert.Equal(t, Form, Default("GET", MIMEJSON)) @@ -378,6 +387,14 @@ func TestBindingFormForType(t *testing.T) { testFormBindingForType(t, "GET", "/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3", "", "", "Float64") + + testFormBindingForType(t, "POST", + "/", "/", + "ptr_bar=test", "bar2=test", "Ptr") + + testFormBindingForType(t, "GET", + "/?ptr_bar=test", "/?bar2=test", + "", "", "Ptr") } func TestBindingQuery(t *testing.T) { @@ -944,6 +961,27 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooStructForSliceMapType{} err := b.Bind(req, &obj) assert.Error(t, err) + case "Ptr": + obj := FooStructForStringPtrType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Nil(t, obj.PtrFoo) + assert.Equal(t, "test", *obj.PtrBar) + + obj = FooStructForStringPtrType{} + obj.PtrBar = new(string) + err = b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, "test", *obj.PtrBar) + + objErr := FooStructForMapPtrType{} + err = b.Bind(req, &objErr) + assert.Error(t, err) + + obj = FooStructForStringPtrType{} + req = requestWithBody(method, badPath, badBody) + err = b.Bind(req, &obj) + assert.Error(t, err) } } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 7c680d9..7d5c102 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -112,6 +112,12 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V return setFloatField(val, 64, structField) case reflect.String: structField.SetString(val) + case reflect.Ptr: + if !structField.Elem().IsValid() { + structField.Set(reflect.New(structField.Type().Elem())) + } + structFieldElem := structField.Elem() + return setWithProperType(structFieldElem.Kind(), val, structFieldElem) default: return errors.New("Unknown type") } From bd4f73af679e7d645f6d0277258fa360eda96f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 1 May 2018 14:24:18 +0800 Subject: [PATCH 155/912] support struct pointer (#1342) * support struct pointer * add readme --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++ binding/binding_test.go | 50 ++++++++++++++++++++++ binding/form_mapping.go | 9 +++- 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aba9ef3..cff9bc8 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [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) - [Testing](#testing) - [Users](#users--) @@ -1461,6 +1462,98 @@ func loadTemplate() (*template.Template, error) { 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. + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/binding/binding_test.go b/binding/binding_test.go index e29e6df..d6899db 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -74,6 +74,18 @@ type FooStructForSliceType struct { SliceFoo []int `form:"slice_foo"` } +type FooStructForStructType struct { + StructFoo struct { + Idx int `form:"idx"` + } +} + +type FooStructForStructPointerType struct { + StructPointerFoo *struct { + Name string `form:"name"` + } +} + type FooStructForSliceMapType struct { // Unknown type: not support map SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` @@ -395,6 +407,22 @@ func TestBindingFormForType(t *testing.T) { testFormBindingForType(t, "GET", "/?ptr_bar=test", "/?bar2=test", "", "", "Ptr") + + testFormBindingForType(t, "POST", + "/", "/", + "idx=123", "id1=1", "Struct") + + testFormBindingForType(t, "GET", + "/?idx=123", "/?id1=1", + "", "", "Struct") + + testFormBindingForType(t, "POST", + "/", "/", + "name=thinkerou", "name1=ou", "StructPointer") + + testFormBindingForType(t, "GET", + "/?name=thinkerou", "/?name1=ou", + "", "", "StructPointer") } func TestBindingQuery(t *testing.T) { @@ -953,6 +981,28 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) + case "Struct": + obj := FooStructForStructType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, + struct { + Idx int "form:\"idx\"" + }(struct { + Idx int "form:\"idx\"" + }{Idx: 123}), + obj.StructFoo) + case "StructPointer": + obj := FooStructForStructPointerType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, + struct { + Name string "form:\"name\"" + }(struct { + Name string "form:\"name\"" + }{Name: "thinkerou"}), + *obj.StructPointerFoo) case "Map": obj := FooStructForMapType{} err := b.Bind(req, &obj) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 7d5c102..6ff726d 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -36,9 +36,16 @@ func mapForm(ptr interface{}, form map[string][]string) error { if inputFieldName == "" { inputFieldName = typeField.Name - // if "form" tag is nil, we inspect if the field is a struct. + // if "form" tag is nil, we inspect if the field is a struct or struct pointer. // this would not make sense for JSON parsing but it does for a form // since data is flatten + if structFieldKind == reflect.Ptr { + if !structField.Elem().IsValid() { + structField.Set(reflect.New(structField.Type().Elem())) + } + structField = structField.Elem() + structFieldKind = structField.Kind() + } if structFieldKind == reflect.Struct { err := mapForm(structField.Addr().Interface(), form) if err != nil { From 6e09ef03b0d292c0a636edae960694d9ae3ade8d Mon Sep 17 00:00:00 2001 From: Aurelien Regat-Barrel Date: Fri, 11 May 2018 03:57:21 +0200 Subject: [PATCH 156/912] Fix typo in panic() message (extra single quote) (#1352) Also fix the same typo in a comment --- tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index 20e3704..b653066 100644 --- a/tree.go +++ b/tree.go @@ -232,7 +232,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { } else if i == len(path) { // Make node a (in-path) leaf if n.handlers != nil { - panic("handlers are already registered for path ''" + fullPath + "'") + panic("handlers are already registered for path '" + fullPath + "'") } n.handlers = handlers } @@ -247,7 +247,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { var offset int // already handled bytes of the path - // find prefix until first wildcard (beginning with ':'' or '*'') + // find prefix until first wildcard (beginning with ':' or '*') for i, max := 0, len(path); numParams > 0; i++ { c := path[i] if c != ':' && c != '*' { From 995fa8e9ce404a7a8dc293c90ec018367639ffa9 Mon Sep 17 00:00:00 2001 From: JINNOUCHI Yasushi Date: Fri, 11 May 2018 11:33:33 +0900 Subject: [PATCH 157/912] Fix #216: Enable to call binding multiple times in some formats (#1341) * Add interface to read body bytes in binding * Add BindingBody implementation for some binding * Fix to use `BindBodyBytesKey` for key * Revert "Fix to use `BindBodyBytesKey` for key" This reverts commit 2c82901ceab6ae53730a3cfcd9839bee11a08f13. * Use private-like key for body bytes * Add tests for BindingBody & ShouldBindBodyWith * Add note for README * Remove redundant space between sentences --- README.md | 59 ++++++++++++++++++++++++++ binding/binding.go | 7 ++++ binding/binding_body_test.go | 67 ++++++++++++++++++++++++++++++ binding/json.go | 12 +++++- binding/msgpack.go | 13 +++++- binding/protobuf.go | 15 +++---- binding/xml.go | 11 ++++- context.go | 25 +++++++++++ context_test.go | 80 ++++++++++++++++++++++++++++++++++++ 9 files changed, 279 insertions(+), 10 deletions(-) create mode 100644 binding/binding_body_test.go diff --git a/README.md b/README.md index cff9bc8..70a5a13 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [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--) @@ -1554,6 +1555,64 @@ type StructZ struct { 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. diff --git a/binding/binding.go b/binding/binding.go index 646eb80..1a98477 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -29,6 +29,13 @@ type Binding interface { 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 diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go new file mode 100644 index 0000000..c41d9f8 --- /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: ` + + FOO +`, + }, + { + name: "MsgPack binding", + binding: MsgPack, + body: msgPackBody(t), + }, + } { + t.Logf("testing: %s", tt.name) + req := requestWithBody("POST", "/", tt.body) + form := FooStruct{} + body, _ := ioutil.ReadAll(req.Body) + assert.NoError(t, tt.binding.BindBody(body, &form)) + assert.Equal(t, FooStruct{"FOO"}, form) + } +} + +func msgPackBody(t *testing.T) string { + test := FooStruct{"FOO"} + h := new(codec.MsgpackHandle) + buf := bytes.NewBuffer(nil) + assert.NoError(t, codec.NewEncoder(buf, h).Encode(test)) + return buf.String() +} + +func TestBindingBodyProto(t *testing.T) { + test := example.Test{ + Label: proto.String("FOO"), + } + data, _ := proto.Marshal(&test) + req := requestWithBody("POST", "/", string(data)) + form := example.Test{} + body, _ := ioutil.ReadAll(req.Body) + assert.NoError(t, ProtoBuf.BindBody(body, &form)) + assert.Equal(t, test, form) +} diff --git a/binding/json.go b/binding/json.go index e928a8c..fea17bb 100644 --- a/binding/json.go +++ b/binding/json.go @@ -5,6 +5,8 @@ package binding import ( + "bytes" + "io" "net/http" "github.com/gin-gonic/gin/json" @@ -22,7 +24,15 @@ func (jsonBinding) Name() string { } func (jsonBinding) Bind(req *http.Request, obj interface{}) error { - decoder := json.NewDecoder(req.Body) + return decodeJSON(req.Body, obj) +} + +func (jsonBinding) BindBody(body []byte, obj interface{}) error { + return decodeJSON(bytes.NewReader(body), obj) +} + +func decodeJSON(r io.Reader, obj interface{}) error { + decoder := json.NewDecoder(r) if EnableDecoderUseNumber { decoder.UseNumber() } diff --git a/binding/msgpack.go b/binding/msgpack.go index 7faea4b..b7f7319 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -5,6 +5,8 @@ package binding import ( + "bytes" + "io" "net/http" "github.com/ugorji/go/codec" @@ -17,7 +19,16 @@ func (msgpackBinding) Name() string { } func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { - if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil { + return decodeMsgPack(req.Body, obj) +} + +func (msgpackBinding) BindBody(body []byte, obj interface{}) error { + return decodeMsgPack(bytes.NewReader(body), obj) +} + +func decodeMsgPack(r io.Reader, obj interface{}) error { + cdc := new(codec.MsgpackHandle) + if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil { return err } return validate(obj) diff --git a/binding/protobuf.go b/binding/protobuf.go index c7eb84e..540e9c1 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -17,19 +17,20 @@ func (protobufBinding) Name() string { return "protobuf" } -func (protobufBinding) Bind(req *http.Request, obj interface{}) error { - +func (b protobufBinding) Bind(req *http.Request, obj interface{}) error { buf, err := ioutil.ReadAll(req.Body) if err != nil { return err } + return b.BindBody(buf, obj) +} - if err = proto.Unmarshal(buf, obj.(proto.Message)); err != nil { +func (protobufBinding) BindBody(body []byte, obj interface{}) error { + if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil { return err } - - //Here it's same to return validate(obj), but util now we cann't add `binding:""` to the struct - //which automatically generate by gen-proto + // Here it's same to return validate(obj), but util now we cann't add + // `binding:""` to the struct which automatically generate by gen-proto return nil - //return validate(obj) + // return validate(obj) } diff --git a/binding/xml.go b/binding/xml.go index f84a6b7..4e90114 100644 --- a/binding/xml.go +++ b/binding/xml.go @@ -5,7 +5,9 @@ package binding import ( + "bytes" "encoding/xml" + "io" "net/http" ) @@ -16,7 +18,14 @@ func (xmlBinding) Name() string { } func (xmlBinding) Bind(req *http.Request, obj interface{}) error { - decoder := xml.NewDecoder(req.Body) + return decodeXML(req.Body, obj) +} + +func (xmlBinding) BindBody(body []byte, obj interface{}) error { + return decodeXML(bytes.NewReader(body), obj) +} +func decodeXML(r io.Reader, obj interface{}) error { + decoder := xml.NewDecoder(r) if err := decoder.Decode(obj); err != nil { return err } diff --git a/context.go b/context.go index be205f4..9a51e46 100755 --- a/context.go +++ b/context.go @@ -31,6 +31,7 @@ const ( MIMEPlain = binding.MIMEPlain MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm + BodyBytesKey = "_gin-gonic/gin/bodybyteskey" ) const abortIndex int8 = math.MaxInt8 / 2 @@ -508,6 +509,30 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { return b.Bind(c.Request, obj) } +// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request +// body into the context, and reuse when it is called again. +// +// NOTE: This method reads the body before binding. So you should use +// ShouldBindWith for better performance if you need to call only once. +func (c *Context) ShouldBindBodyWith( + obj interface{}, bb binding.BindingBody, +) (err error) { + var body []byte + if cb, ok := c.Get(BodyBytesKey); ok { + if cbb, ok := cb.([]byte); ok { + body = cbb + } + } + if body == nil { + body, err = ioutil.ReadAll(c.Request.Body) + if err != nil { + return err + } + c.Set(BodyBytesKey, body) + } + return bb.BindBody(body, obj) +} + // ClientIP implements a best effort algorithm to return the real client IP, it parses // X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy. // Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP. diff --git a/context_test.go b/context_test.go index 841d8af..e94866b 100644 --- a/context_test.go +++ b/context_test.go @@ -18,6 +18,7 @@ import ( "time" "github.com/gin-contrib/sse" + "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) @@ -1334,6 +1335,85 @@ func TestContextBadAutoShouldBind(t *testing.T) { assert.False(t, c.IsAborted()) } +func TestContextShouldBindBodyWith(t *testing.T) { + type typeA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` + } + type typeB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` + } + for _, tt := range []struct { + name string + bindingA, bindingB binding.BindingBody + bodyA, bodyB string + }{ + { + name: "JSON & JSON", + bindingA: binding.JSON, + bindingB: binding.JSON, + bodyA: `{"foo":"FOO"}`, + bodyB: `{"bar":"BAR"}`, + }, + { + name: "JSON & XML", + bindingA: binding.JSON, + bindingB: binding.XML, + bodyA: `{"foo":"FOO"}`, + bodyB: ` + + BAR +`, + }, + { + name: "XML & XML", + bindingA: binding.XML, + bindingB: binding.XML, + bodyA: ` + + FOO +`, + bodyB: ` + + BAR +`, + }, + } { + t.Logf("testing: %s", tt.name) + // bodyA to typeA and typeB + { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest( + "POST", "http://example.com", bytes.NewBufferString(tt.bodyA), + ) + // When it binds to typeA and typeB, it finds the body is + // not typeB but typeA. + objA := typeA{} + assert.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + assert.Equal(t, typeA{"FOO"}, objA) + objB := typeB{} + assert.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + assert.NotEqual(t, typeB{"BAR"}, objB) + } + // bodyB to typeA and typeB + { + // When it binds to typeA and typeB, it finds the body is + // not typeA but typeB. + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest( + "POST", "http://example.com", bytes.NewBufferString(tt.bodyB), + ) + objA := typeA{} + assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + assert.NotEqual(t, typeA{"FOO"}, objA) + objB := typeB{} + assert.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + assert.Equal(t, typeB{"BAR"}, objB) + } + } +} + func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) From 5636afe02d00308bc5ade9dd80acfa7646b608df Mon Sep 17 00:00:00 2001 From: chainhelen Date: Fri, 11 May 2018 22:40:33 +0800 Subject: [PATCH 158/912] fix bug, return err when failed binding bool (#1350) * fix bug, return err when failed binding bool * add test, return err when failed binding bool --- binding/binding_test.go | 23 +++++++++++++++++++++++ binding/form_mapping.go | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index d6899db..936deac 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -91,6 +91,10 @@ type FooStructForSliceMapType struct { SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` } +type FooStructForBoolType struct { + BoolFoo bool `form:"bool_foo"` +} + type FooBarStructForIntType struct { IntFoo int `form:"int_foo"` IntBar int `form:"int_bar" binding:"required"` @@ -449,6 +453,12 @@ func TestBindingQueryFail2(t *testing.T) { "map_foo=unused", "") } +func TestBindingQueryBoolFail(t *testing.T) { + testQueryBindingBoolFail(t, "GET", + "/?bool_foo=fasl", "/?bar2=foo", + "bool_foo=unused", "") +} + func TestBindingXML(t *testing.T) { testBodyBinding(t, XML, "xml", @@ -1063,6 +1073,19 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str assert.Error(t, err) } +func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody string) { + b := Query + assert.Equal(t, "query", b.Name()) + + obj := FooStructForBoolType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) +} + func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 6ff726d..3f6b9bf 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -161,7 +161,7 @@ func setBoolField(val string, field reflect.Value) error { if err == nil { field.SetBool(boolVal) } - return nil + return err } func setFloatField(val string, bitSize int, field reflect.Value) error { From bf7803815b0baa22ff7a10457932882dfbf09925 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Lebreton Date: Sat, 12 May 2018 05:00:42 +0200 Subject: [PATCH 159/912] Serve easily dynamic files with `DataFromReader` context method (#1304) * Add DataFromReader context method * Replace fmt by strconv.FormatInt * Add pull request link to README --- README.md | 27 +++++++++++++++++++++++++++ context.go | 10 ++++++++++ context_test.go | 19 +++++++++++++++++++ render/reader.go | 36 ++++++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 23 +++++++++++++++++++++++ 6 files changed, 116 insertions(+) create mode 100644 render/reader.go diff --git a/README.md b/README.md index 70a5a13..51a807b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [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) @@ -901,6 +902,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() diff --git a/context.go b/context.go index 9a51e46..462357e 100755 --- a/context.go +++ b/context.go @@ -741,6 +741,16 @@ func (c *Context) Data(code int, contentType string, data []byte) { }) } +// DataFromReader writes the specified reader into the body stream and updates the HTTP code. +func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) { + c.Render(code, render.Reader{ + Headers: extraHeaders, + ContentType: contentType, + ContentLength: contentLength, + Reader: reader, + }) +} + // File writes the specified file into the body stream in a efficient way. func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) diff --git a/context_test.go b/context_test.go index e94866b..12e02fa 100644 --- a/context_test.go +++ b/context_test.go @@ -1471,3 +1471,22 @@ func TestContextGetRawData(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "Fetch binary post data", string(data)) } + +func TestContextRenderDataFromReader(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + body := "#!PNG some raw data" + reader := strings.NewReader(body) + contentLength := int64(len(body)) + contentType := "image/png" + extraHeaders := map[string]string{"Content-Disposition": `attachment; filename="gopher.png"`} + + c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, body, w.Body.String()) + assert.Equal(t, contentType, w.HeaderMap.Get("Content-Type")) + assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) + assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) +} diff --git a/render/reader.go b/render/reader.go new file mode 100644 index 0000000..be2132c --- /dev/null +++ b/render/reader.go @@ -0,0 +1,36 @@ +package render + +import ( + "io" + "net/http" + "strconv" +) + +type Reader struct { + ContentType string + ContentLength int64 + Reader io.Reader + Headers map[string]string +} + +// Render (Reader) writes data with custom ContentType and headers. +func (r Reader) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) + r.writeHeaders(w, r.Headers) + _, err = io.Copy(w, r.Reader) + return +} + +func (r Reader) WriteContentType(w http.ResponseWriter) { + writeContentType(w, []string{r.ContentType}) +} + +func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { + header := w.Header() + for k, v := range headers { + if val := header[k]; len(val) == 0 { + header[k] = []string{v} + } + } +} diff --git a/render/render.go b/render/render.go index 578f278..7caf9bb 100755 --- a/render/render.go +++ b/render/render.go @@ -25,6 +25,7 @@ var ( _ HTMLRender = HTMLProduction{} _ Render = YAML{} _ Render = MsgPack{} + _ Render = Reader{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_test.go b/render/render_test.go index c7b51ef..40ec806 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -11,6 +11,8 @@ import ( "html/template" "net/http" "net/http/httptest" + "strconv" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -384,3 +386,24 @@ func TestRenderHTMLDebugPanics(t *testing.T) { } assert.Panics(t, func() { htmlRender.Instance("", nil) }) } + +func TestRenderReader(t *testing.T) { + w := httptest.NewRecorder() + + body := "#!PNG some raw data" + headers := make(map[string]string) + headers["Content-Disposition"] = `attachment; filename="filename.png"` + + err := (Reader{ + ContentLength: int64(len(body)), + ContentType: "image/png", + Reader: strings.NewReader(body), + Headers: headers, + }).Render(w) + + assert.NoError(t, err) + assert.Equal(t, body, w.Body.String()) + assert.Equal(t, "image/png", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, strconv.Itoa(len(body)), w.HeaderMap.Get("Content-Length")) + assert.Equal(t, headers["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) +} From 07cbe116a058f13026fa6b23f675155968d47cc3 Mon Sep 17 00:00:00 2001 From: chainhelen Date: Wed, 30 May 2018 09:19:04 +0800 Subject: [PATCH 160/912] Add and fix the explanation of `HandleContext` (#1371) Reference this issue #1323 1. There isn't any eg about `HandleContext` 2. The `c.Request.Path` of `HandleContext` Comment is not right Based on the above two points, I pull this request. If you think it's unnecessary, I will close this. Thx. --- README.md | 16 ++++++++++++++-- gin.go | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 51a807b..fd5c175 100644 --- a/README.md +++ b/README.md @@ -1082,14 +1082,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 diff --git a/gin.go b/gin.go index 4205eff..b91fc2f 100644 --- a/gin.go +++ b/gin.go @@ -329,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() From c2f083fc953073a5cfdcdd386fb3c7d1f004c2cc Mon Sep 17 00:00:00 2001 From: MW Lim Date: Thu, 31 May 2018 11:41:45 +0800 Subject: [PATCH 161/912] minor typo in routergroup.go (#1360) --- routergroup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 5e681c1..53045d1 100644 --- a/routergroup.go +++ b/routergroup.go @@ -139,7 +139,7 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou return group.returnObj() } -// StaticFile registers a single route in order to server a single file of the local filesystem. +// StaticFile registers a single route in order to serve a single file of the local filesystem. // router.StaticFile("favicon.ico", "./resources/favicon.ico") func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { From caf3e350a548af1add9def68087ac53d1d000caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 31 May 2018 14:13:40 +0800 Subject: [PATCH 162/912] doc: update readme for adding binding about skip validate (#1359) * update readme for adding binding about skip validate * update readme for adding binding about skip validate --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index fd5c175..0bac65b 100644 --- a/README.md +++ b/README.md @@ -572,6 +572,10 @@ $ curl -v -X POST \ {"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). From 87d536c00120e6631cd7e6ff100739858e07e002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 21 Jun 2018 09:31:43 +0800 Subject: [PATCH 163/912] utils: use strings.Split instead of strings.IndexByte (#1400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And I test them benchmark: code: ```go # stringsbench.go package stringsbench import "strings" func index(part string) string { if index := strings.IndexByte(part, ';'); index >= 0 { if part := strings.TrimSpace(strings.Split(part, ";")[0]); part != "" { return part[0:index] } } return "" } func split(part string) string { return strings.Split(part, ";")[0] } ``` ```go # stringsbench_test.go package stringsbench import ( "testing" ) func BenchmarkIndex(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { index("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") } }) } func BenchmarkSplit(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { split("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") } }) } ``` And the result: ```shell ➜ strings go test --bench=. goos: darwin goarch: amd64 BenchmarkIndex-8 30000000 46.1 ns/op BenchmarkSplit-8 50000000 35.9 ns/op PASS ok _/Users/tianou/strings 3.271s ➜ strings go test --bench=. goos: darwin goarch: amd64 BenchmarkIndex-8 30000000 44.2 ns/op BenchmarkSplit-8 50000000 34.7 ns/op PASS ok _/Users/tianou/strings 3.156s ➜ strings go test --bench=. goos: darwin goarch: amd64 BenchmarkIndex-8 30000000 45.6 ns/op BenchmarkSplit-8 50000000 35.3 ns/op PASS ok _/Users/tianou/strings 3.230s ``` --- utils.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/utils.go b/utils.go index 278029d..bf32c77 100644 --- a/utils.go +++ b/utils.go @@ -103,10 +103,7 @@ func parseAccept(acceptHeader string) []string { parts := strings.Split(acceptHeader, ",") out := make([]string, 0, len(parts)) for _, part := range parts { - if index := strings.IndexByte(part, ';'); index >= 0 { - part = part[0:index] - } - if part = strings.TrimSpace(part); part != "" { + if part = strings.TrimSpace(strings.Split(part, ";")[0]); part != "" { out = append(out, part) } } From bf85b32c1d11dcfa2202daa6d1ad2addae2d1619 Mon Sep 17 00:00:00 2001 From: htobenothing Date: Thu, 21 Jun 2018 09:53:52 +0800 Subject: [PATCH 164/912] Add Pusher() function for support http2 server push (#1273) gin already support http2, while previously not support server push. Add Pusher() function to extend the ResponseWriter interface. ```golang // get http.Pusher if pusher := c.Writer.Pusher(); pusher != nil { // use pusher.Push() to do server push } ``` screen shot 2018-03-07 at 11 20 49 pm --- README.md | 50 ++++++++++++++++++++++++ examples/http-pusher/assets/app.js | 1 + examples/http-pusher/main.go | 41 +++++++++++++++++++ examples/http-pusher/testdata/ca.pem | 15 +++++++ examples/http-pusher/testdata/server.key | 16 ++++++++ examples/http-pusher/testdata/server.pem | 16 ++++++++ response_writer.go | 2 +- response_writer_1.7.go | 12 ++++++ response_writer_1.8.go | 25 ++++++++++++ 9 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 examples/http-pusher/assets/app.js create mode 100644 examples/http-pusher/main.go create mode 100644 examples/http-pusher/testdata/ca.pem create mode 100644 examples/http-pusher/testdata/server.key create mode 100644 examples/http-pusher/testdata/server.pem create mode 100644 response_writer_1.7.go create mode 100644 response_writer_1.8.go diff --git a/README.md b/README.md index 0bac65b..c2917a5 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) + - [http2 server push](#http2-server-push) - [Testing](#testing) - [Users](#users--) @@ -1656,6 +1657,55 @@ enough to call binding at once. can be called by `c.ShouldBind()` multiple times without any damage to performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). +### http2 server push + +http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. + +[embedmd]:# (examples/http-pusher/main.go go) +```go +package main + +import ( + "html/template" + "log" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(200, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} +``` + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/examples/http-pusher/assets/app.js b/examples/http-pusher/assets/app.js new file mode 100644 index 0000000..05271b6 --- /dev/null +++ b/examples/http-pusher/assets/app.js @@ -0,0 +1 @@ +console.log("http2 pusher"); diff --git a/examples/http-pusher/main.go b/examples/http-pusher/main.go new file mode 100644 index 0000000..d4f33aa --- /dev/null +++ b/examples/http-pusher/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "html/template" + "log" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(200, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} diff --git a/examples/http-pusher/testdata/ca.pem b/examples/http-pusher/testdata/ca.pem new file mode 100644 index 0000000..6c8511a --- /dev/null +++ b/examples/http-pusher/testdata/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla +Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 +YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT +BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 ++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu +g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd +Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau +sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m +oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG +Dfcog5wrJytaQ6UA0wE= +-----END CERTIFICATE----- diff --git a/examples/http-pusher/testdata/server.key b/examples/http-pusher/testdata/server.key new file mode 100644 index 0000000..143a5b8 --- /dev/null +++ b/examples/http-pusher/testdata/server.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD +M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf +3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY +AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm +V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY +tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p +dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q +K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR +81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff +DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd +aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 +ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 +XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe +F98XJ7tIFfJq +-----END PRIVATE KEY----- diff --git a/examples/http-pusher/testdata/server.pem b/examples/http-pusher/testdata/server.pem new file mode 100644 index 0000000..f3d43fc --- /dev/null +++ b/examples/http-pusher/testdata/server.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET +MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx +MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV +BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 +ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco +LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg +zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd +9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw +CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy +em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G +CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 +hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh +y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 +-----END CERTIFICATE----- diff --git a/response_writer.go b/response_writer.go index 232f00a..14b1cfc 100644 --- a/response_writer.go +++ b/response_writer.go @@ -16,7 +16,7 @@ const ( defaultStatus = 200 ) -type ResponseWriter interface { +type responseWriterBase interface { http.ResponseWriter http.Hijacker http.Flusher diff --git a/response_writer_1.7.go b/response_writer_1.7.go new file mode 100644 index 0000000..801d196 --- /dev/null +++ b/response_writer_1.7.go @@ -0,0 +1,12 @@ +// +build !go1.8 + +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +// ResponseWriter ... +type ResponseWriter interface { + responseWriterBase +} diff --git a/response_writer_1.8.go b/response_writer_1.8.go new file mode 100644 index 0000000..527c003 --- /dev/null +++ b/response_writer_1.8.go @@ -0,0 +1,25 @@ +// +build go1.8 + +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "net/http" +) + +// ResponseWriter ... +type ResponseWriter interface { + responseWriterBase + // get the http.Pusher for server push + Pusher() http.Pusher +} + +func (w *responseWriter) Pusher() (pusher http.Pusher) { + if pusher, ok := w.ResponseWriter.(http.Pusher); ok { + return pusher + } + return nil +} From 737d2fb7ab4ad358c5fa01b7a08ce346dcaebd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 22 Jun 2018 09:51:06 +0800 Subject: [PATCH 165/912] add grpc example (#1401) use grpc helloworld example. --- Makefile | 3 +- coverage.sh | 2 +- examples/grpc/README.md | 19 ++++ examples/grpc/gin/main.go | 46 +++++++++ examples/grpc/grpc/server.go | 34 +++++++ examples/grpc/pb/helloworld.pb.go | 151 ++++++++++++++++++++++++++++++ examples/grpc/pb/helloworld.proto | 37 ++++++++ 7 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 examples/grpc/README.md create mode 100644 examples/grpc/gin/main.go create mode 100644 examples/grpc/grpc/server.go create mode 100644 examples/grpc/pb/helloworld.pb.go create mode 100644 examples/grpc/pb/helloworld.proto diff --git a/Makefile b/Makefile index 5468563..51b9969 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ GOFMT ?= gofmt "-s" PACKAGES ?= $(shell go list ./... | grep -v /vendor/) +VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/) GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") all: install @@ -26,7 +27,7 @@ fmt-check: fi; vet: - go vet $(PACKAGES) + go vet $(VETPACKAGES) deps: @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ diff --git a/coverage.sh b/coverage.sh index 81437f9..4d1ee03 100644 --- a/coverage.sh +++ b/coverage.sh @@ -4,7 +4,7 @@ set -e echo "mode: count" > coverage.out -for d in $(go list ./... | grep -E 'gin$|binding$|render$'); do +for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do go test -v -covermode=count -coverprofile=profile.out $d if [ -f profile.out ]; then cat profile.out | grep -v "mode:" >> coverage.out diff --git a/examples/grpc/README.md b/examples/grpc/README.md new file mode 100644 index 0000000..a96d3c1 --- /dev/null +++ b/examples/grpc/README.md @@ -0,0 +1,19 @@ +## How to run this example + +1. run grpc server + +```sh +$ go run grpc/server.go +``` + +2. run gin server + +```sh +$ go run gin/main.go +``` + +3. use curl command to test it + +```sh +$ curl -v 'http://localhost:8052/rest/n/thinkerou' +``` diff --git a/examples/grpc/gin/main.go b/examples/grpc/gin/main.go new file mode 100644 index 0000000..c21692a --- /dev/null +++ b/examples/grpc/gin/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "log" + "net/http" + + "github.com/gin-gonic/gin" + pb "github.com/gin-gonic/gin/examples/grpc/pb" + "google.golang.org/grpc" +) + +func main() { + // Set up a connection to the server. + conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) + if err != nil { + log.Fatalf("did not connect: %v", err) + } + defer conn.Close() + client := pb.NewGreeterClient(conn) + + // Set up a http setver. + r := gin.Default() + r.GET("/rest/n/:name", func(c *gin.Context) { + name := c.Param("name") + + // Contact the server and print out its response. + req := &pb.HelloRequest{Name: name} + res, err := client.SayHello(g, req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "result": fmt.Sprint(res.Message), + }) + }) + + // Run http server + if err := r.Run(":8052"); err != nil { + log.Fatalf("could not run server: %v", err) + } +} diff --git a/examples/grpc/grpc/server.go b/examples/grpc/grpc/server.go new file mode 100644 index 0000000..d9bf9fc --- /dev/null +++ b/examples/grpc/grpc/server.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + "net" + + pb "github.com/gin-gonic/gin/examples/grpc/pb" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +// server is used to implement helloworld.GreeterServer. +type server struct{} + +// SayHello implements helloworld.GreeterServer +func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { + return &pb.HelloReply{Message: "Hello " + in.Name}, nil +} + +func main() { + lis, err := net.Listen("tcp", ":50051") + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + s := grpc.NewServer() + pb.RegisterGreeterServer(s, &server{}) + + // Register reflection service on gRPC server. + reflection.Register(s) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} diff --git a/examples/grpc/pb/helloworld.pb.go b/examples/grpc/pb/helloworld.pb.go new file mode 100644 index 0000000..c8c8942 --- /dev/null +++ b/examples/grpc/pb/helloworld.pb.go @@ -0,0 +1,151 @@ +// Code generated by protoc-gen-go. +// source: helloworld.proto +// DO NOT EDIT! + +/* +Package helloworld is a generated protocol buffer package. + +It is generated from these files: + helloworld.proto + +It has these top-level messages: + HelloRequest + HelloReply +*/ +package helloworld + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// The request message containing the user's name. +type HelloRequest struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *HelloRequest) Reset() { *m = HelloRequest{} } +func (m *HelloRequest) String() string { return proto.CompactTextString(m) } +func (*HelloRequest) ProtoMessage() {} +func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +// The response message containing the greetings +type HelloReply struct { + Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` +} + +func (m *HelloReply) Reset() { *m = HelloReply{} } +func (m *HelloReply) String() string { return proto.CompactTextString(m) } +func (*HelloReply) ProtoMessage() {} +func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func init() { + proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest") + proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Greeter service + +type GreeterClient interface { + // Sends a greeting + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) +} + +type greeterClient struct { + cc *grpc.ClientConn +} + +func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + out := new(HelloReply) + err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Greeter service + +type GreeterServer interface { + // Sends a greeting + SayHello(context.Context, *HelloRequest) (*HelloReply, error) +} + +func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { + s.RegisterService(&_Greeter_serviceDesc, srv) +} + +func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/helloworld.Greeter/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Greeter_serviceDesc = grpc.ServiceDesc{ + ServiceName: "helloworld.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "helloworld.proto", +} + +func init() { proto.RegisterFile("helloworld.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 174 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, + 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88, + 0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, + 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, + 0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, + 0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a, + 0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64, + 0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x00, 0xad, 0x50, 0x62, 0x70, 0x32, 0xe0, 0x92, 0xce, 0xcc, 0xd7, + 0x4b, 0x2f, 0x2a, 0x48, 0xd6, 0x4b, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x2d, 0x46, 0x52, 0xeb, + 0xc4, 0x0f, 0x56, 0x1c, 0x0e, 0x62, 0x07, 0x80, 0xbc, 0x14, 0xc0, 0x98, 0xc4, 0x06, 0xf6, 0x9b, + 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00, +} diff --git a/examples/grpc/pb/helloworld.proto b/examples/grpc/pb/helloworld.proto new file mode 100644 index 0000000..d79a6a0 --- /dev/null +++ b/examples/grpc/pb/helloworld.proto @@ -0,0 +1,37 @@ +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} From 605aa1c30f196733a90a4f773b691d2350a30e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 22 Jun 2018 23:44:45 +0800 Subject: [PATCH 166/912] example: fix typo for grpc (#1405) sorry...fixed #1403 --- examples/grpc/gin/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/grpc/gin/main.go b/examples/grpc/gin/main.go index c21692a..edc1ca9 100644 --- a/examples/grpc/gin/main.go +++ b/examples/grpc/gin/main.go @@ -26,7 +26,7 @@ func main() { // Contact the server and print out its response. req := &pb.HelloRequest{Name: name} - res, err := client.SayHello(g, req) + res, err := client.SayHello(c, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), From 8035359102b5384ad1f860675c85c0238b06b6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 23 Jun 2018 00:08:58 +0800 Subject: [PATCH 167/912] use strings.Split instead of strings.IndexByte (#1406) --- context.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) mode change 100755 => 100644 context.go diff --git a/context.go b/context.go old mode 100755 new mode 100644 index 462357e..6a84de5 --- a/context.go +++ b/context.go @@ -539,14 +539,10 @@ func (c *Context) ShouldBindBodyWith( func (c *Context) ClientIP() string { if c.engine.ForwardedByClientIP { clientIP := c.requestHeader("X-Forwarded-For") - if index := strings.IndexByte(clientIP, ','); index >= 0 { - clientIP = clientIP[0:index] + clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0]) + if clientIP == "" { + clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) } - clientIP = strings.TrimSpace(clientIP) - if clientIP != "" { - return clientIP - } - clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) if clientIP != "" { return clientIP } From 760d0574db7432376d91b53dba9146c1f32d28c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 23 Jun 2018 00:45:43 +0800 Subject: [PATCH 168/912] vendor: remove vendor package from example folder (#1402) updated `vendor.json` is ok. but set `ignore test` in `vendor.json`, `x/net/context` package only use in `context_test.go`, I don't know why vendor still need it. please @appleboy review the pull request, thanks a lot. --- vendor/vendor.json | 61 ---------------------------------------------- 1 file changed, 61 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 5ace49d..c34c2de 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,61 +9,30 @@ "revision": "346938d642f2ec3594ed81d874461961cd0faa76", "revisionTime": "2016-10-29T20:57:26Z" }, - { - "checksumSHA1": "7c3FuEadBInl/4ExSrB7iJMXpe4=", - "path": "github.com/dustin/go-broadcast", - "revision": "3bdf6d4a7164a50bc19d5f230e2981d87d2584f1", - "revisionTime": "2014-06-27T04:00:55Z" - }, { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", "path": "github.com/gin-contrib/sse", "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", "revisionTime": "2017-01-09T09:34:21Z" }, - { - "checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=", - "path": "github.com/gin-gonic/autotls", - "revision": "8ca25fbde72bb72a00466215b94b489c71fcb815", - "revisionTime": "2017-09-16T16:54:15Z" - }, { "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", "path": "github.com/golang/protobuf/proto", "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revisionTime": "2017-06-01T23:02:30Z" }, - { - "checksumSHA1": "Cq9h7eDNXXyR/qJPvO8/Rk4pmFg=", - "path": "github.com/jessevdk/go-assets", - "revision": "4f4301a06e153ff90e17793577ab6bf79f8dc5c5", - "revisionTime": "2016-09-21T14:41:39Z" - }, { "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "path": "github.com/json-iterator/go", "revision": "36b14963da70d11297d313183d7e6388c8510e1e", "revisionTime": "2017-08-29T15:58:51Z" }, - { - "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", - "path": "github.com/manucorporat/stats", - "revision": "8f2d6ace262eba462e9beb552382c98be51d807b", - "revisionTime": "2015-05-31T20:46:25Z" - }, { "checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=", "path": "github.com/mattn/go-isatty", "revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", "revisionTime": "2017-03-07T16:30:44Z" }, - { - "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", - "comment": "v1.0.0", - "path": "github.com/pmezard/go-difflib/difflib", - "revision": "792786c7400a136282c1664665ae0a8db921c6c2", - "revisionTime": "2016-01-10T10:55:54Z" - }, { "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", "comment": "v1.1.4", @@ -71,30 +40,12 @@ "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revisionTime": "2016-09-25T22:06:09Z" }, - { - "checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=", - "path": "github.com/thinkerou/favicon", - "revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93", - "revisionTime": "2017-07-10T14:05:20Z" - }, { "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", "path": "github.com/ugorji/go/codec", "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", "revisionTime": "2017-02-15T20:11:44Z" }, - { - "checksumSHA1": "W0j4I7QpxXlChjyhAojZmFjU6Bg=", - "path": "golang.org/x/crypto/acme", - "revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d", - "revisionTime": "2017-06-19T06:03:41Z" - }, - { - "checksumSHA1": "TrKJW+flz7JulXU3sqnBJjGzgQc=", - "path": "golang.org/x/crypto/acme/autocert", - "revision": "adbae1b6b6fb4b02448a0fc0dbbc9ba2b95b294d", - "revisionTime": "2017-06-19T06:03:41Z" - }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "comment": "release-branch.go1.7", @@ -102,18 +53,6 @@ "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revisionTime": "2016-10-18T08:54:36Z" }, - { - "checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=", - "path": "golang.org/x/sync/errgroup", - "revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690", - "revisionTime": "2017-07-19T03:38:01Z" - }, - { - "checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=", - "path": "golang.org/x/sys/unix", - "revision": "99f16d856c9836c42d24e7ab64ea72916925fa97", - "revisionTime": "2017-03-08T15:04:45Z" - }, { "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", "comment": "v8.18.1", From 1f59bad84b577735b27a1ee855f01ed2f50cd88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 23 Jun 2018 11:06:27 +0800 Subject: [PATCH 169/912] add an edge case from httprouter (#1407) --- path.go | 2 +- path_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/path.go b/path.go index ed63ad1..1c2b849 100644 --- a/path.go +++ b/path.go @@ -41,7 +41,7 @@ func cleanPath(p string) string { buf[0] = '/' } - trailing := n > 2 && p[n-1] == '/' + trailing := n > 1 && p[n-1] == '/' // A bit more clunky without a 'lazybuf' like the path package, but the loop // gets completely inlined (bufApp). So in contrast to the path package this diff --git a/path_test.go b/path_test.go index 4a6d945..c1e6ed4 100644 --- a/path_test.go +++ b/path_test.go @@ -24,6 +24,7 @@ var cleanTests = []struct { // missing root {"", "/"}, + {"a/", "/a/"}, {"abc", "/abc"}, {"abc/def", "/abc/def"}, {"a/b/c", "/a/b/c"}, From 6c6d97ba2e7eeaf78e20fc4344b21c4e057e2a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 26 Jun 2018 17:21:32 +0800 Subject: [PATCH 170/912] remove hardcode instead of http status value (#1411) --- auth.go | 3 ++- context.go | 6 +++--- gin.go | 12 ++++++------ recovery.go | 3 ++- response_writer.go | 2 +- routergroup.go | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/auth.go b/auth.go index c214309..9ed81b5 100644 --- a/auth.go +++ b/auth.go @@ -7,6 +7,7 @@ package gin import ( "crypto/subtle" "encoding/base64" + "net/http" "strconv" ) @@ -51,7 +52,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc { if !found { // Credentials doesn't match, we return 401 and abort handlers chain. c.Header("WWW-Authenticate", realm) - c.AbortWithStatus(401) + c.AbortWithStatus(http.StatusUnauthorized) return } diff --git a/context.go b/context.go index 6a84de5..65fbb62 100644 --- a/context.go +++ b/context.go @@ -474,7 +474,7 @@ func (c *Context) BindQuery(obj interface{}) error { // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { if err = c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(400, err).SetType(ErrorTypeBind) + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) } return @@ -589,9 +589,9 @@ func bodyAllowedForStatus(status int) bool { switch { case status >= 100 && status <= 199: return false - case status == 204: + case status == http.StatusNoContent: return false - case status == 304: + case status == http.StatusNotModified: return false } return true diff --git a/gin.go b/gin.go index b91fc2f..65d6d44 100644 --- a/gin.go +++ b/gin.go @@ -378,14 +378,14 @@ func (engine *Engine) handleHTTPRequest(c *Context) { if tree.method != httpMethod { if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { c.handlers = engine.allNoMethod - serveError(c, 405, default405Body) + serveError(c, http.StatusMethodNotAllowed, default405Body) return } } } } c.handlers = engine.allNoRoute - serveError(c, 404, default404Body) + serveError(c, http.StatusNotFound, default404Body) } var mimePlain = []string{MIMEPlain} @@ -406,9 +406,9 @@ func serveError(c *Context, code int, defaultMessage []byte) { func redirectTrailingSlash(c *Context) { req := c.Request path := req.URL.Path - code := 301 // Permanent redirect, request with GET method + code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { - code = 307 + code = http.StatusTemporaryRedirect } if length := len(path); length > 1 && path[length-1] == '/' { @@ -430,9 +430,9 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { trailingSlash, ) if found { - code := 301 // Permanent redirect, request with GET method + code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { - code = 307 + code = http.StatusTemporaryRedirect } req.URL.Path = string(fixedPath) debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) diff --git a/recovery.go b/recovery.go index dcbeba7..61c5bd5 100644 --- a/recovery.go +++ b/recovery.go @@ -10,6 +10,7 @@ import ( "io" "io/ioutil" "log" + "net/http" "net/http/httputil" "runtime" "time" @@ -41,7 +42,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { httprequest, _ := httputil.DumpRequest(c.Request, false) logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) } - c.AbortWithStatus(500) + c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() diff --git a/response_writer.go b/response_writer.go index 14b1cfc..166ae8c 100644 --- a/response_writer.go +++ b/response_writer.go @@ -13,7 +13,7 @@ import ( const ( noWritten = -1 - defaultStatus = 200 + defaultStatus = http.StatusOK ) type responseWriterBase interface { diff --git a/routergroup.go b/routergroup.go index 53045d1..876a61b 100644 --- a/routergroup.go +++ b/routergroup.go @@ -184,7 +184,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS _, nolisting := fs.(*onlyfilesFS) return func(c *Context) { if nolisting { - c.Writer.WriteHeader(404) + c.Writer.WriteHeader(http.StatusNotFound) } fileServer.ServeHTTP(c.Writer, c.Request) } From c00f21ff239822d013b80e54bd403fbc08a0b5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 26 Jun 2018 18:56:43 +0800 Subject: [PATCH 171/912] add go version prerequisite and debug warning (#1394) * add go version prerequisite and debug warning * merge duplicate content * remove duplicate content --- README.md | 113 +++++++++++++++++++++++++++----------------------- debug.go | 3 ++ debug_test.go | 2 +- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index c2917a5..e09a51f 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,11 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ## Contents +- [Installation](#installation) +- [Prerequisite](#prerequisite) - [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) @@ -58,6 +59,64 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Testing](#testing) - [Users](#users--) +## Installation + +To install Gin package, you need to install Go and set your Go workspace first. + +1. Download and install it: + +```sh +$ go get -u github.com/gin-gonic/gin +``` + +2. Import it in your code: + +```go +import "github.com/gin-gonic/gin" +``` + +3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. + +```go +import "net/http" +``` + +### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) + +1. `go get` govendor + +```sh +$ go get github.com/kardianos/govendor +``` +2. Create your project folder and `cd` inside + +```sh +$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" +``` + +3. Vendor init your project and add gin + +```sh +$ govendor init +$ govendor fetch github.com/gin-gonic/gin@v1.2 +``` + +4. Copy a starting template inside your project + +```sh +$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go +``` + +5. Run your project + +```sh +$ go run main.go +``` + +## Prerequisite + +Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + ## Quick start ```sh @@ -135,58 +194,6 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 - [x] Battle tested - [x] API frozen, new releases will not break your code. -## Start using it - -1. Download and install it: - -```sh -$ go get github.com/gin-gonic/gin -``` - -2. Import it in your code: - -```go -import "github.com/gin-gonic/gin" -``` - -3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. - -```go -import "net/http" -``` - -### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) - -1. `go get` govendor - -```sh -$ go get github.com/kardianos/govendor -``` -2. Create your project folder and `cd` inside - -```sh -$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" -``` - -3. Vendor init your project and add gin - -```sh -$ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.2 -``` - -4. Copy a starting template inside your project - -```sh -$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go -``` - -5. Run your project - -```sh -$ go run main.go -``` - ## 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. diff --git a/debug.go b/debug.go index 897c494..f11156b 100644 --- a/debug.go +++ b/debug.go @@ -47,6 +47,9 @@ func debugPrint(format string, values ...interface{}) { } func debugPrintWARNINGDefault() { + debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + +`) debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. `) diff --git a/debug_test.go b/debug_test.go index dfd54c8..440173b 100644 --- a/debug_test.go +++ b/debug_test.go @@ -92,7 +92,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { defer teardown() debugPrintWARNINGDefault() - assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) } func TestDebugPrintWARNINGNew(t *testing.T) { From eb9f313144e6768d91421855810ed5937ce348c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 28 Jun 2018 17:08:09 +0800 Subject: [PATCH 172/912] add comment for context (#1413) ref #1075 annotation from go context source. --- context.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/context.go b/context.go index 65fbb62..6fc5d25 100644 --- a/context.go +++ b/context.go @@ -836,18 +836,33 @@ func (c *Context) SetAccepted(formats ...string) { /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. func (c *Context) Deadline() (deadline time.Time, ok bool) { return } +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. func (c *Context) Done() <-chan struct{} { return nil } +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. func (c *Context) Err() error { return nil } +// Value returns the value associated with this context for key, or nil +// if no value is associated with key. Successive calls to Value with +// the same key returns the same result. func (c *Context) Value(key interface{}) interface{} { if key == 0 { return c.Request From cdd02fa9d65c15de42f1a4522fcb9af28fe56cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 1 Jul 2018 21:10:48 +0800 Subject: [PATCH 173/912] update error(err) to err (#1416) the pull request update `return error(err)` to `return err`, and remove `kindOfData`. --- binding/binding.go | 5 ++--- binding/default_validator.go | 19 ++++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 1a98477..3a2aad9 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -4,10 +4,9 @@ package binding -import ( - "net/http" -) +import "net/http" +// Content-Type MIME of the most common data formats. const ( MIMEJSON = "application/json" MIMEHTML = "text/html" diff --git a/binding/default_validator.go b/binding/default_validator.go index c67aa8a..e7a302d 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -18,11 +18,17 @@ type defaultValidator struct { var _ StructValidator = &defaultValidator{} +// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. func (v *defaultValidator) ValidateStruct(obj interface{}) error { - if kindOfData(obj) == reflect.Struct { + value := reflect.ValueOf(obj) + valueType := value.Kind() + if valueType == reflect.Ptr { + valueType = value.Elem().Kind() + } + if valueType == reflect.Struct { v.lazyinit() if err := v.validate.Struct(obj); err != nil { - return error(err) + return err } } return nil @@ -43,12 +49,3 @@ func (v *defaultValidator) lazyinit() { v.validate = validator.New(config) }) } - -func kindOfData(data interface{}) reflect.Kind { - value := reflect.ValueOf(data) - valueType := value.Kind() - if valueType == reflect.Ptr { - valueType = value.Elem().Kind() - } - return valueType -} From 1c4cbfae59e0f62a6812b7808839eed45b66515f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 2 Jul 2018 11:06:56 +0800 Subject: [PATCH 174/912] chore: remove duplicate code (#1418) --- gin.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/gin.go b/gin.go index 65d6d44..3ee8018 100644 --- a/gin.go +++ b/gin.go @@ -171,14 +171,14 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { func (engine *Engine) LoadHTMLGlob(pattern string) { left := engine.delims.Left right := engine.delims.Right + templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) if IsDebugging() { - debugPrintLoadTemplate(template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))) + debugPrintLoadTemplate(templ) engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} return } - templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) engine.SetHTMLTemplate(templ) } @@ -425,11 +425,7 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request path := req.URL.Path - fixedPath, found := root.findCaseInsensitivePath( - cleanPath(path), - trailingSlash, - ) - if found { + if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok { code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { code = http.StatusTemporaryRedirect From d17a12591fb637da47576b8669fabb8752fe656d Mon Sep 17 00:00:00 2001 From: vz Date: Tue, 3 Jul 2018 15:39:18 +0800 Subject: [PATCH 175/912] update assert param(expect, actual) position (#1421) - update assert param(expect, actual) position --- errors_test.go | 18 +++++++++--------- response_writer_test.go | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/errors_test.go b/errors_test.go index a666d7c..0626611 100644 --- a/errors_test.go +++ b/errors_test.go @@ -19,17 +19,17 @@ func TestError(t *testing.T) { Type: ErrorTypePrivate, } assert.Equal(t, err.Error(), baseError.Error()) - assert.Equal(t, err.JSON(), H{"error": baseError.Error()}) + assert.Equal(t, H{"error": baseError.Error()}, err.JSON()) assert.Equal(t, err.SetType(ErrorTypePublic), err) - assert.Equal(t, err.Type, ErrorTypePublic) + assert.Equal(t, ErrorTypePublic, err.Type) assert.Equal(t, err.SetMeta("some data"), err) - assert.Equal(t, err.Meta, "some data") - assert.Equal(t, err.JSON(), H{ + assert.Equal(t, "some data", err.Meta) + assert.Equal(t, H{ "error": baseError.Error(), "meta": "some data", - }) + }, err.JSON()) jsonBytes, _ := json.Marshal(err) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) @@ -38,22 +38,22 @@ func TestError(t *testing.T) { "status": "200", "data": "some data", }) - assert.Equal(t, err.JSON(), H{ + assert.Equal(t, H{ "error": baseError.Error(), "status": "200", "data": "some data", - }) + }, err.JSON()) err.SetMeta(H{ "error": "custom error", "status": "200", "data": "some data", }) - assert.Equal(t, err.JSON(), H{ + assert.Equal(t, H{ "error": "custom error", "status": "200", "data": "some data", - }) + }, err.JSON()) type customError struct { status string diff --git a/response_writer_test.go b/response_writer_test.go index cec2733..4289a7c 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -51,7 +51,7 @@ func TestResponseWriterWriteHeader(t *testing.T) { w.WriteHeader(300) assert.False(t, w.Written()) assert.Equal(t, 300, w.Status()) - assert.NotEqual(t, testWritter.Code, 300) + assert.NotEqual(t, 300, testWritter.Code) w.WriteHeader(-1) assert.Equal(t, 300, w.Status()) From 85221af84cf6ee99dc900bc41912abc09acfc1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rex=20Lee=28=E6=9D=8E=E4=BF=8A=29?= Date: Tue, 3 Jul 2018 17:17:08 +0800 Subject: [PATCH 176/912] add json ASCII string render (#1358) add a json render that rendering json as ASCII string --- README.md | 23 +++++++++++++++++++++++ context.go | 6 ++++++ context_test.go | 11 +++++++++++ render/json.go | 32 ++++++++++++++++++++++++++++++++ render/render_test.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+) diff --git a/README.md b/README.md index e09a51f..9389646 100644 --- a/README.md +++ b/README.md @@ -900,6 +900,29 @@ func main() { } ``` +#### AsciiJSON + +Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters. + +```go +func main() { + r := gin.Default() + + r.GET("/someJSON", func(c *gin.Context) { + data := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} + c.AsciiJSON(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + ### Serving static files ```go diff --git a/context.go b/context.go index 6fc5d25..d0b4e87 100644 --- a/context.go +++ b/context.go @@ -704,6 +704,12 @@ func (c *Context) JSON(code int, obj interface{}) { c.Render(code, render.JSON{Data: obj}) } +// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. +// It also sets the Content-Type as "application/json". +func (c *Context) AsciiJSON(code int, obj interface{}) { + c.Render(code, render.AsciiJSON{Data: obj}) +} + // XML serializes the given struct as XML into the response body. // It also sets the Content-Type as "application/xml". func (c *Context) XML(code int, obj interface{}) { diff --git a/context_test.go b/context_test.go index 12e02fa..6f37682 100644 --- a/context_test.go +++ b/context_test.go @@ -686,6 +686,17 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +func TestContextRenderNoContentAsciiJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.AsciiJSON(http.StatusNoContent, []string{"lang", "Go语言"}) + + assert.Equal(t, http.StatusNoContent, w.Code) + assert.Empty(t, w.Body.String()) + assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type")) +} + // Tests that the response executes the templates // and responds with Content-Type set to text/html func TestContextRenderHTML(t *testing.T) { diff --git a/render/json.go b/render/json.go index 3a2e8b2..6e5089a 100755 --- a/render/json.go +++ b/render/json.go @@ -6,6 +6,7 @@ package render import ( "bytes" + "fmt" "html/template" "net/http" @@ -30,10 +31,15 @@ type JsonpJSON struct { Data interface{} } +type AsciiJSON struct { + Data interface{} +} + type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} +var jsonAsciiContentType = []string{"application/json"} func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { @@ -112,3 +118,29 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonpContentType) } + +func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + ret, err := json.Marshal(r.Data) + if err != nil { + return err + } + + var buffer bytes.Buffer + for _, r := range string(ret) { + cvt := "" + if r < 128 { + cvt = string(r) + } else { + cvt = fmt.Sprintf("\\u%04x", int64(r)) + } + buffer.WriteString(cvt) + } + + w.Write(buffer.Bytes()) + return nil +} + +func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonAsciiContentType) +} diff --git a/render/render_test.go b/render/render_test.go index 40ec806..2f72844 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -167,6 +167,35 @@ func TestRenderJsonpJSONFail(t *testing.T) { assert.Error(t, err) } +func TestRenderAsciiJSON(t *testing.T) { + w1 := httptest.NewRecorder() + data1 := map[string]interface{}{ + "lang": "GO语言", + "tag": "
", + } + + err := (AsciiJSON{data1}).Render(w1) + + assert.NoError(t, err) + assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) + assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) + + w2 := httptest.NewRecorder() + data2 := float64(3.1415926) + + err = (AsciiJSON{data2}).Render(w2) + assert.NoError(t, err) + assert.Equal(t, "3.1415926", w2.Body.String()) +} + +func TestRenderAsciiJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + assert.Error(t, (AsciiJSON{data}).Render(w)) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal From 220e8d34538f5dfdcc299dda79bf83cf52e633f6 Mon Sep 17 00:00:00 2001 From: solos Date: Sat, 21 Jul 2018 00:52:55 +0800 Subject: [PATCH 177/912] return json if jsonp has not callback (#1438) return json if jsonp has not callback --- context.go | 7 ++++++- context_test.go | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index d0b4e87..720c425 100644 --- a/context.go +++ b/context.go @@ -695,7 +695,12 @@ func (c *Context) SecureJSON(code int, obj interface{}) { // It add padding to response body to request data from a server residing in a different domain than the client. // It also sets the Content-Type as "application/javascript". func (c *Context) JSONP(code int, obj interface{}) { - c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj}) + callback := c.DefaultQuery("callback", "") + if callback == "" { + c.Render(code, render.JSON{Data: obj}) + } else { + c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) + } } // JSON serializes the given struct as JSON into the response body. diff --git a/context_test.go b/context_test.go index 6f37682..e29569f 100644 --- a/context_test.go +++ b/context_test.go @@ -596,6 +596,20 @@ func TestContextRenderJSONP(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// Tests that the response is serialized as JSONP +// and Content-Type is set to application/json +func TestContextRenderJSONPWithoutCallback(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("GET", "http://example.com", nil) + + c.JSONP(201, H{"foo": "bar"}) + + assert.Equal(t, 201, w.Code) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + // Tests that no JSON is rendered if code is 204 func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() From 631cfbd1ef2374f5a01f5d17ed8c8f08779ad6fe Mon Sep 17 00:00:00 2001 From: Dmitry Dorogin Date: Sun, 5 Aug 2018 08:29:26 +0300 Subject: [PATCH 178/912] Simplify context error (#1431) Hello! Looking through context package and found a little bit complicated switch block. And tried to make it easier. Thanks! --- context.go | 9 ++++----- ginS/gins.go | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 720c425..779484b 100644 --- a/context.go +++ b/context.go @@ -159,16 +159,15 @@ func (c *Context) Error(err error) *Error { if err == nil { panic("err is nil") } - var parsedError *Error - switch err.(type) { - case *Error: - parsedError = err.(*Error) - default: + + parsedError, ok := err.(*Error) + if !ok { parsedError = &Error{ Err: err, Type: ErrorTypePrivate, } } + c.Errors = append(c.Errors, parsedError) return parsedError } diff --git a/ginS/gins.go b/ginS/gins.go index ee00b38..a7686f2 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -128,7 +128,7 @@ func Run(addr ...string) (err error) { // RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine undefinitelly unless an error happens. -func RunTLS(addr string, certFile string, keyFile string) (err error) { +func RunTLS(addr, certFile, keyFile string) (err error) { return engine().RunTLS(addr, certFile, keyFile) } From 647535cd9b780b6ccbeccfedfba36896e1a1da37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 6 Aug 2018 12:07:11 +0800 Subject: [PATCH 179/912] Support map as query string or post form parameters (#1383) * support query map * add GetQueryMap and unittest * support post-form map * add readme for query map * attempt to fix bug for post-form map when go version is 1.6 * remove duplicate code * remove comment --- README.md | 29 +++++++++++++++++++++++++++++ context.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ context_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9389646..11c91c9 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Querystring parameters](#querystring-parameters) - [Multipart/Urlencoded Form](#multiparturlencoded-form) - [Another example: query + post form](#another-example-query--post-form) + - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) - [Upload files](#upload-files) - [Grouping routes](#grouping-routes) - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) @@ -323,6 +324,34 @@ func main() { id: 1234; page: 1; name: manu; message: this_is_great ``` +### Map as querystring or postform parameters + +``` +POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +names[first]=thinkerou&names[second]=tianou +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + ids := c.QueryMap("ids") + names := c.PostFormMap("names") + + fmt.Printf("ids: %v; names: %v", ids, names) + }) + router.Run(":8080") +} +``` + +``` +ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] +``` + ### Upload files #### Single file diff --git a/context.go b/context.go index 779484b..724ded7 100644 --- a/context.go +++ b/context.go @@ -360,6 +360,18 @@ func (c *Context) GetQueryArray(key string) ([]string, bool) { return []string{}, false } +// QueryMap returns a map for a given query key. +func (c *Context) QueryMap(key string) map[string]string { + dicts, _ := c.GetQueryMap(key) + return dicts +} + +// GetQueryMap returns a map for a given query key, plus a boolean value +// whether at least one value exists for the given key. +func (c *Context) GetQueryMap(key string) (map[string]string, bool) { + return c.get(c.Request.URL.Query(), key) +} + // PostForm returns the specified key from a POST urlencoded form or multipart form // when it exists, otherwise it returns an empty string `("")`. func (c *Context) PostForm(key string) string { @@ -415,6 +427,42 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) { return []string{}, false } +// PostFormMap returns a map for a given form key. +func (c *Context) PostFormMap(key string) map[string]string { + dicts, _ := c.GetPostFormMap(key) + return dicts +} + +// GetPostFormMap returns a map for a given form key, plus a boolean value +// whether at least one value exists for the given key. +func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { + req := c.Request + req.ParseForm() + req.ParseMultipartForm(c.engine.MaxMultipartMemory) + dicts, exist := c.get(req.PostForm, key) + + if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil { + dicts, exist = c.get(req.MultipartForm.Value, key) + } + + return dicts, exist +} + +// get is an internal method and returns a map which satisfy conditions. +func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) { + dicts := make(map[string]string) + exist := false + for k, v := range m { + if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { + if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { + exist = true + dicts[k[i+1:][:j]] = v[0] + } + } + } + return dicts, exist +} + // FormFile returns the first file for the provided form key. func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { _, fh, err := c.Request.FormFile(name) diff --git a/context_test.go b/context_test.go index e29569f..589ab33 100644 --- a/context_test.go +++ b/context_test.go @@ -47,6 +47,8 @@ func createMultipartRequest() *http.Request { must(mw.WriteField("time_local", "31/12/2016 14:55")) must(mw.WriteField("time_utc", "31/12/2016 14:55")) must(mw.WriteField("time_location", "31/12/2016 14:55")) + must(mw.WriteField("names[a]", "thinkerou")) + must(mw.WriteField("names[b]", "tianou")) req, err := http.NewRequest("POST", "/", body) must(err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -371,7 +373,8 @@ func TestContextQuery(t *testing.T) { func TestContextQueryAndPostForm(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") - c.Request, _ = http.NewRequest("POST", "/?both=GET&id=main&id=omit&array[]=first&array[]=second", body) + c.Request, _ = http.NewRequest("POST", + "/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) assert.Equal(t, "bar", c.DefaultPostForm("foo", "none")) @@ -439,6 +442,30 @@ func TestContextQueryAndPostForm(t *testing.T) { values = c.QueryArray("both") assert.Equal(t, 1, len(values)) assert.Equal(t, "GET", values[0]) + + dicts, ok := c.GetQueryMap("ids") + assert.True(t, ok) + assert.Equal(t, "hi", dicts["a"]) + assert.Equal(t, "3.14", dicts["b"]) + + dicts, ok = c.GetQueryMap("nokey") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts, ok = c.GetQueryMap("both") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts, ok = c.GetQueryMap("array") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts = c.QueryMap("ids") + assert.Equal(t, "hi", dicts["a"]) + assert.Equal(t, "3.14", dicts["b"]) + + dicts = c.QueryMap("nokey") + assert.Equal(t, 0, len(dicts)) } func TestContextPostFormMultipart(t *testing.T) { @@ -515,6 +542,22 @@ func TestContextPostFormMultipart(t *testing.T) { values = c.PostFormArray("foo") assert.Equal(t, 1, len(values)) assert.Equal(t, "bar", values[0]) + + dicts, ok := c.GetPostFormMap("names") + assert.True(t, ok) + assert.Equal(t, "thinkerou", dicts["a"]) + assert.Equal(t, "tianou", dicts["b"]) + + dicts, ok = c.GetPostFormMap("nokey") + assert.False(t, ok) + assert.Equal(t, 0, len(dicts)) + + dicts = c.PostFormMap("names") + assert.Equal(t, "thinkerou", dicts["a"]) + assert.Equal(t, "tianou", dicts["b"]) + + dicts = c.PostFormMap("nokey") + assert.Equal(t, 0, len(dicts)) } func TestContextSetCookie(t *testing.T) { From e2b4cf6e2dffb4e59f95f96b78e25e65f9037dec Mon Sep 17 00:00:00 2001 From: grapeVine Date: Mon, 6 Aug 2018 23:08:01 +0800 Subject: [PATCH 180/912] interface implement type check (#1459) interface implement type check --- render/render.go | 1 + 1 file changed, 1 insertion(+) diff --git a/render/render.go b/render/render.go index 7caf9bb..4ff1c7b 100755 --- a/render/render.go +++ b/render/render.go @@ -26,6 +26,7 @@ var ( _ Render = YAML{} _ Render = MsgPack{} _ Render = Reader{} + _ Render = AsciiJSON{} ) func writeContentType(w http.ResponseWriter, value []string) { From 9b7e7bdce6e073d47635d57fbacfb3205cb3127e Mon Sep 17 00:00:00 2001 From: Dmitry Dorogin Date: Tue, 7 Aug 2018 01:44:32 +0300 Subject: [PATCH 181/912] Add tests for context.Stream (#1433) --- context_test.go | 36 ++++++++++++++++++++++++++++++++++++ test_helpers.go | 21 +++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/context_test.go b/context_test.go index 589ab33..40ae5bf 100644 --- a/context_test.go +++ b/context_test.go @@ -21,6 +21,7 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" + "io" ) var _ context.Context = &Context{} @@ -1558,3 +1559,38 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) } + +func TestContextStream(t *testing.T) { + w := CreateTestResponseRecorder() + c, _ := CreateTestContext(w) + + stopStream := true + c.Stream(func(w io.Writer) bool { + defer func() { + stopStream = false + }() + + w.Write([]byte("test")) + + return stopStream + }) + + assert.Equal(t, "testtest", w.Body.String()) +} + +func TestContextStreamWithClientGone(t *testing.T) { + w := CreateTestResponseRecorder() + c, _ := CreateTestContext(w) + + c.Stream(func(writer io.Writer) bool { + defer func() { + w.closeClient() + }() + + writer.Write([]byte("test")) + + return true + }) + + assert.Equal(t, "test", w.Body.String()) +} diff --git a/test_helpers.go b/test_helpers.go index 2aed37f..554568d 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -6,6 +6,7 @@ package gin import ( "net/http" + "net/http/httptest" ) // CreateTestContext returns a fresh engine and context for testing purposes @@ -16,3 +17,23 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { c.writermem.reset(w) return } + +type TestResponseRecorder struct { + *httptest.ResponseRecorder + closeChannel chan bool +} + +func (r *TestResponseRecorder) CloseNotify() <-chan bool { + return r.closeChannel +} + +func (r *TestResponseRecorder) closeClient() { + r.closeChannel <- true +} + +func CreateTestResponseRecorder() *TestResponseRecorder { + return &TestResponseRecorder{ + httptest.NewRecorder(), + make(chan bool, 1), + } +} From 0552c3bc3a6de6b802684b024ae49c3195cc70dd Mon Sep 17 00:00:00 2001 From: zhanweidu Date: Tue, 7 Aug 2018 12:41:28 +0800 Subject: [PATCH 182/912] flush operation will overwrite the origin status code (#1460) The status of responseWriter will be overwrite if flush was called. This is caused by the Flush of http.response.Flush(). --- response_writer.go | 1 + response_writer_test.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/response_writer.go b/response_writer.go index 166ae8c..923b53f 100644 --- a/response_writer.go +++ b/response_writer.go @@ -110,5 +110,6 @@ func (w *responseWriter) CloseNotify() <-chan bool { // Flush implements the http.Flush interface. func (w *responseWriter) Flush() { + w.WriteHeaderNow() w.ResponseWriter.(http.Flusher).Flush() } diff --git a/response_writer_test.go b/response_writer_test.go index 4289a7c..fcc48b3 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -113,3 +113,19 @@ func TestResponseWriterHijack(t *testing.T) { w.Flush() } + +func TestResponseWriterFlush(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writer := &responseWriter{} + writer.reset(w) + + writer.WriteHeader(http.StatusInternalServerError) + writer.Flush() + })) + defer testServer.Close() + + // should return 500 + resp, err := http.Get(testServer.URL) + assert.NoError(t, err) + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) +} From 9666ba67383f6cc7bed3bb18691c2382717d51ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 7 Aug 2018 13:49:31 +0800 Subject: [PATCH 183/912] chore: update top bar header (#1461) --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 11c91c9..c2cfe2d 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) - [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) - [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) - [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) - [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) +[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) +[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) +[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. @@ -1811,7 +1812,7 @@ func TestPingRoute(t *testing.T) { } ``` -## Users [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) +## Users Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. From 1f1bc429ed7a4d591a7374f0e267b59747b177bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 9 Aug 2018 17:20:06 +0800 Subject: [PATCH 184/912] chore: add test case for source/function of recovery.go (#1467) --- recovery_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/recovery_test.go b/recovery_test.go index de3b62a..fa065b1 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -41,3 +41,23 @@ func TestPanicWithAbort(t *testing.T) { // TEST assert.Equal(t, 400, w.Code) } + +func TestSource(t *testing.T) { + bs := source(nil, 0) + assert.Equal(t, []byte("???"), bs) + + in := [][]byte{ + []byte("Hello world."), + []byte("Hi, gin.."), + } + bs = source(in, 10) + assert.Equal(t, []byte("???"), bs) + + bs = source(in, 1) + assert.Equal(t, []byte("Hello world."), bs) +} + +func TestFunction(t *testing.T) { + bs := function(1) + assert.Equal(t, []byte("???"), bs) +} From 8fc8ce047243dbbbc5eb3d01b9fd026fc3b08c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 10 Aug 2018 20:50:23 +0800 Subject: [PATCH 185/912] small enhance for cleanPath (#1469) from httprouter patch: https://github.com/julienschmidt/httprouter/pull/243 --- path.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/path.go b/path.go index 1c2b849..d1f5962 100644 --- a/path.go +++ b/path.go @@ -59,11 +59,11 @@ func cleanPath(p string) string { case p[r] == '.' && p[r+1] == '/': // . element - r++ + r += 2 case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): // .. element: remove to last / - r += 2 + r += 3 if w > 1 { // can backtrack From 7e64d32269d817b645d5d4694100a8a8ad6960cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 10:12:33 +0800 Subject: [PATCH 186/912] Attempt to fix #1462 (#1463) #1462 --- context_test.go | 20 ++++++++++++++++++++ test_helpers.go | 25 +------------------------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/context_test.go b/context_test.go index 40ae5bf..0185507 100644 --- a/context_test.go +++ b/context_test.go @@ -1560,6 +1560,26 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) } +type TestResponseRecorder struct { + *httptest.ResponseRecorder + closeChannel chan bool +} + +func (r *TestResponseRecorder) CloseNotify() <-chan bool { + return r.closeChannel +} + +func (r *TestResponseRecorder) closeClient() { + r.closeChannel <- true +} + +func CreateTestResponseRecorder() *TestResponseRecorder { + return &TestResponseRecorder{ + httptest.NewRecorder(), + make(chan bool, 1), + } +} + func TestContextStream(t *testing.T) { w := CreateTestResponseRecorder() c, _ := CreateTestContext(w) diff --git a/test_helpers.go b/test_helpers.go index 554568d..3a7a5dd 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -4,10 +4,7 @@ package gin -import ( - "net/http" - "net/http/httptest" -) +import "net/http" // CreateTestContext returns a fresh engine and context for testing purposes func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { @@ -17,23 +14,3 @@ func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { c.writermem.reset(w) return } - -type TestResponseRecorder struct { - *httptest.ResponseRecorder - closeChannel chan bool -} - -func (r *TestResponseRecorder) CloseNotify() <-chan bool { - return r.closeChannel -} - -func (r *TestResponseRecorder) closeClient() { - r.closeChannel <- true -} - -func CreateTestResponseRecorder() *TestResponseRecorder { - return &TestResponseRecorder{ - httptest.NewRecorder(), - make(chan bool, 1), - } -} From e5bb4f62a284426de199d68d71900fcbd1285691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 21:17:57 +0800 Subject: [PATCH 187/912] chore: add return or remove else for reduce indent (#1470) --- gin.go | 69 ++++++++++++++++++++++++++------------------------ routes_test.go | 10 ++++++++ 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/gin.go b/gin.go index 3ee8018..fc4d656 100644 --- a/gin.go +++ b/gin.go @@ -349,38 +349,40 @@ func (engine *Engine) handleHTTPRequest(c *Context) { // Find root of the tree for the given HTTP method t := engine.trees for i, tl := 0, len(t); i < tl; i++ { - if t[i].method == httpMethod { - root := t[i].root - // Find route in tree - handlers, params, tsr := root.getValue(path, c.Params, unescape) - if handlers != nil { - c.handlers = handlers - c.Params = params - c.Next() - c.writermem.WriteHeaderNow() + if t[i].method != httpMethod { + continue + } + root := t[i].root + // Find route in tree + handlers, params, tsr := root.getValue(path, c.Params, unescape) + if handlers != nil { + c.handlers = handlers + c.Params = params + c.Next() + c.writermem.WriteHeaderNow() + return + } + if httpMethod != "CONNECT" && path != "/" { + if tsr && engine.RedirectTrailingSlash { + redirectTrailingSlash(c) return } - if httpMethod != "CONNECT" && path != "/" { - if tsr && engine.RedirectTrailingSlash { - redirectTrailingSlash(c) - return - } - if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { - return - } + if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { + return } - break } + break } if engine.HandleMethodNotAllowed { for _, tree := range engine.trees { - if tree.method != httpMethod { - if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { - c.handlers = engine.allNoMethod - serveError(c, http.StatusMethodNotAllowed, default405Body) - return - } + if tree.method == httpMethod { + continue + } + if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { + c.handlers = engine.allNoMethod + serveError(c, http.StatusMethodNotAllowed, default405Body) + return } } } @@ -393,14 +395,16 @@ var mimePlain = []string{MIMEPlain} func serveError(c *Context, code int, defaultMessage []byte) { c.writermem.status = code c.Next() - if !c.writermem.Written() { - if c.writermem.Status() == code { - c.writermem.Header()["Content-Type"] = mimePlain - c.Writer.Write(defaultMessage) - } else { - c.writermem.WriteHeaderNow() - } + if c.writermem.Written() { + return + } + if c.writermem.Status() == code { + c.writermem.Header()["Content-Type"] = mimePlain + c.Writer.Write(defaultMessage) + return } + c.writermem.WriteHeaderNow() + return } func redirectTrailingSlash(c *Context) { @@ -411,10 +415,9 @@ func redirectTrailingSlash(c *Context) { code = http.StatusTemporaryRedirect } + req.URL.Path = path + "/" if length := len(path); length > 1 && path[length-1] == '/' { req.URL.Path = path[:length-1] - } else { - req.URL.Path = path + "/" } debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) diff --git a/routes_test.go b/routes_test.go index 8129390..a2389f9 100644 --- a/routes_test.go +++ b/routes_test.go @@ -333,6 +333,16 @@ func TestRouteNotAllowedEnabled(t *testing.T) { assert.Equal(t, http.StatusTeapot, w.Code) } +func TestRouteNotAllowedEnabled2(t *testing.T) { + router := New() + router.HandleMethodNotAllowed = true + // add one methodTree to trees + router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}}) + router.GET("/path2", func(c *Context) {}) + w := performRequest(router, "POST", "/path2") + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) +} + func TestRouteNotAllowedDisabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = false From 202db4fb117768795c0fcc909cc9c3d75b9172b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 21:38:07 +0800 Subject: [PATCH 188/912] improve utils code coverage (#1473) --- utils_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/utils_test.go b/utils_test.go index 3d019e7..95b5de5 100644 --- a/utils_test.go +++ b/utils_test.go @@ -5,6 +5,8 @@ package gin import ( + "bytes" + "encoding/xml" "fmt" "net/http" "testing" @@ -124,3 +126,14 @@ func TestBindMiddleware(t *testing.T) { Bind(&bindTestStruct{}) }) } + +func TestMarshalXMLforH(t *testing.T) { + h := H{ + "": "test", + } + var b bytes.Buffer + enc := xml.NewEncoder(&b) + var x xml.StartElement + e := h.MarshalXML(enc, x) + assert.Error(t, e) +} From 1ae32f3a2cfa53d1ae3e5c10a72841673bedc449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 22:02:37 +0800 Subject: [PATCH 189/912] improve render code coverage (#1474) all code coverage > 99% --- render/render_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/render/render_test.go b/render/render_test.go index 2f72844..9e76885 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -158,6 +158,21 @@ func TestRenderJsonpJSON(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } +func TestRenderJsonpJSONError2(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + (JsonpJSON{"", data}).WriteContentType(w) + assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) + + e := (JsonpJSON{"", data}).Render(w) + assert.NoError(t, e) + + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) +} + func TestRenderJsonpJSONFail(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) From 61592134625e0fca1b2de7c22107a01684e8b130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Aug 2018 23:38:31 +0800 Subject: [PATCH 190/912] unify test data (#1417) mkdir a test data dir. --- README.md | 2 +- binding/binding_body_test.go | 6 +++--- binding/binding_test.go | 14 +++++++------- debug_test.go | 2 +- examples/template/main.go | 2 +- gin_test.go | 8 ++++---- render/render_test.go | 4 ++-- .../testdata => testdata/certificate}/cert.pem | 0 .../testdata => testdata/certificate}/key.pem | 0 .../example => testdata/protoexample}/test.pb.go | 6 +++--- .../example => testdata/protoexample}/test.proto | 2 +- {fixtures/basic => testdata/template}/hello.tmpl | 0 {fixtures/basic => testdata/template}/raw.tmpl | 0 13 files changed, 23 insertions(+), 23 deletions(-) rename {fixtures/testdata => testdata/certificate}/cert.pem (100%) rename {fixtures/testdata => testdata/certificate}/key.pem (100%) rename {binding/example => testdata/protoexample}/test.pb.go (94%) rename {binding/example => testdata/protoexample}/test.proto (90%) rename {fixtures/basic => testdata/template}/hello.tmpl (100%) rename {fixtures/basic => testdata/template}/raw.tmpl (100%) diff --git a/README.md b/README.md index c2cfe2d..bf1bbb4 100644 --- a/README.md +++ b/README.md @@ -1117,7 +1117,7 @@ func main() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLFiles("./fixtures/basic/raw.tmpl") + router.LoadHTMLFiles("./testdata/template/raw.tmpl") router.GET("/raw", func(c *gin.Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go index c41d9f8..dfd761e 100644 --- a/binding/binding_body_test.go +++ b/binding/binding_body_test.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "testing" - "github.com/gin-gonic/gin/binding/example" + "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" @@ -55,12 +55,12 @@ func msgPackBody(t *testing.T) string { } func TestBindingBodyProto(t *testing.T) { - test := example.Test{ + test := protoexample.Test{ Label: proto.String("FOO"), } data, _ := proto.Marshal(&test) req := requestWithBody("POST", "/", string(data)) - form := example.Test{} + form := protoexample.Test{} body, _ := ioutil.ReadAll(req.Body) assert.NoError(t, ProtoBuf.BindBody(body, &form)) assert.Equal(t, test, form) diff --git a/binding/binding_test.go b/binding/binding_test.go index 936deac..efe8766 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -14,7 +14,7 @@ import ( "testing" "time" - "github.com/gin-gonic/gin/binding/example" + "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" @@ -562,7 +562,7 @@ func TestBindingFormMultipartFail(t *testing.T) { } func TestBindingProtoBuf(t *testing.T) { - test := &example.Test{ + test := &protoexample.Test{ Label: proto.String("yes"), } data, _ := proto.Marshal(test) @@ -574,7 +574,7 @@ func TestBindingProtoBuf(t *testing.T) { } func TestBindingProtoBufFail(t *testing.T) { - test := &example.Test{ + test := &protoexample.Test{ Label: proto.String("yes"), } data, _ := proto.Marshal(test) @@ -1156,14 +1156,14 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) - obj := example.Test{} + obj := protoexample.Test{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "yes", *obj.Label) - obj = example.Test{} + obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) @@ -1179,7 +1179,7 @@ func (h hook) Read([]byte) (int, error) { func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) - obj := example.Test{} + obj := protoexample.Test{} req := requestWithBody("POST", path, body) req.Body = ioutil.NopCloser(&hook{}) @@ -1187,7 +1187,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body err := b.Bind(req, &obj) assert.Error(t, err) - obj = example.Test{} + obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) diff --git a/debug_test.go b/debug_test.go index 440173b..ed5a6a5 100644 --- a/debug_test.go +++ b/debug_test.go @@ -72,7 +72,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { setup(&w) defer teardown() - templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./fixtures/basic/hello.tmpl")) + templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) debugPrintLoadTemplate(templ) assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String()) } diff --git a/examples/template/main.go b/examples/template/main.go index f9e611d..e20a3b9 100644 --- a/examples/template/main.go +++ b/examples/template/main.go @@ -20,7 +20,7 @@ func main() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLFiles("../../fixtures/basic/raw.tmpl") + router.LoadHTMLFiles("../../testdata/template/raw.tmpl") router.GET("/raw", func(c *gin.Context) { c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ diff --git a/gin_test.go b/gin_test.go index 3ac6057..b3cab71 100644 --- a/gin_test.go +++ b/gin_test.go @@ -30,7 +30,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLFiles("./fixtures/basic/hello.tmpl", "./fixtures/basic/raw.tmpl") + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) @@ -41,7 +41,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool) func() { }) 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") + router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") } else { router.Run(":8888") } @@ -59,7 +59,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { router.SetFuncMap(template.FuncMap{ "formatAsDate": formatAsDate, }) - router.LoadHTMLGlob("./fixtures/basic/*") + router.LoadHTMLGlob("./testdata/template/*") router.GET("/test", func(c *Context) { c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) @@ -70,7 +70,7 @@ func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { }) 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") + router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") } else { router.Run(":8888") } diff --git a/render/render_test.go b/render/render_test.go index 9e76885..8fad12b 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -388,7 +388,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { func TestRenderHTMLDebugFiles(t *testing.T) { w := httptest.NewRecorder() - htmlRender := HTMLDebug{Files: []string{"../fixtures/basic/hello.tmpl"}, + htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"}, Glob: "", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, @@ -407,7 +407,7 @@ func TestRenderHTMLDebugFiles(t *testing.T) { func TestRenderHTMLDebugGlob(t *testing.T) { w := httptest.NewRecorder() htmlRender := HTMLDebug{Files: nil, - Glob: "../fixtures/basic/hello*", + Glob: "../testdata/template/hello*", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, } diff --git a/fixtures/testdata/cert.pem b/testdata/certificate/cert.pem similarity index 100% rename from fixtures/testdata/cert.pem rename to testdata/certificate/cert.pem diff --git a/fixtures/testdata/key.pem b/testdata/certificate/key.pem similarity index 100% rename from fixtures/testdata/key.pem rename to testdata/certificate/key.pem diff --git a/binding/example/test.pb.go b/testdata/protoexample/test.pb.go similarity index 94% rename from binding/example/test.pb.go rename to testdata/protoexample/test.pb.go index 3de8444..21997ca 100644 --- a/binding/example/test.pb.go +++ b/testdata/protoexample/test.pb.go @@ -3,7 +3,7 @@ // DO NOT EDIT! /* -Package example is a generated protocol buffer package. +Package protoexample is a generated protocol buffer package. It is generated from these files: test.proto @@ -11,7 +11,7 @@ It is generated from these files: It has these top-level messages: Test */ -package example +package protoexample import proto "github.com/golang/protobuf/proto" import math "math" @@ -109,5 +109,5 @@ func (m *Test_OptionalGroup) GetRequiredField() string { } func init() { - proto.RegisterEnum("example.FOO", FOO_name, FOO_value) + proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value) } diff --git a/binding/example/test.proto b/testdata/protoexample/test.proto similarity index 90% rename from binding/example/test.proto rename to testdata/protoexample/test.proto index 8ee9800..3e73428 100644 --- a/binding/example/test.proto +++ b/testdata/protoexample/test.proto @@ -1,4 +1,4 @@ -package example; +package protoexample; enum FOO {X=17;}; diff --git a/fixtures/basic/hello.tmpl b/testdata/template/hello.tmpl similarity index 100% rename from fixtures/basic/hello.tmpl rename to testdata/template/hello.tmpl diff --git a/fixtures/basic/raw.tmpl b/testdata/template/raw.tmpl similarity index 100% rename from fixtures/basic/raw.tmpl rename to testdata/template/raw.tmpl From 8aef947f6eeeb47872c2e5910d20258cc6ccf375 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 12 Aug 2018 22:54:22 +0200 Subject: [PATCH 191/912] docs: remove double negative in README.md (#1480) "not match neither" means that it will match. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf1bbb4..28598ba 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ func main() { func main() { router := gin.Default() - // This handler will match /user/john but will not match neither /user/ or /user + // This handler will match /user/john but will not match /user/ or /user router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) From f45c928a156e60e521dceea99a183f9c9cf5b2de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 14 Aug 2018 09:51:56 +0800 Subject: [PATCH 192/912] chore: use http.Status* instead of hard code (#1482) --- auth_test.go | 12 +-- benchmarks_test.go | 6 +- context_test.go | 153 ++++++++++++++------------- examples/basic/main.go | 10 +- examples/basic/main_test.go | 2 +- examples/favicon/main.go | 4 +- examples/http2/main.go | 3 +- examples/realtime-advanced/routes.go | 11 +- examples/realtime-chat/main.go | 5 +- githubapi_test.go | 2 +- logger.go | 7 +- logger_test.go | 17 +-- middleware_test.go | 33 +++--- recovery_test.go | 7 +- render/redirect.go | 2 + render/render_test.go | 4 +- response_writer_test.go | 20 ++-- routergroup_test.go | 7 +- routes_test.go | 88 +++++++-------- utils_test.go | 8 +- 20 files changed, 209 insertions(+), 192 deletions(-) diff --git a/auth_test.go b/auth_test.go index dc8523b..ab7e94b 100644 --- a/auth_test.go +++ b/auth_test.go @@ -93,7 +93,7 @@ func TestBasicAuthSucceed(t *testing.T) { router := New() router.Use(BasicAuth(accounts)) router.GET("/login", func(c *Context) { - c.String(200, c.MustGet(AuthUserKey).(string)) + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() @@ -101,7 +101,7 @@ func TestBasicAuthSucceed(t *testing.T) { req.Header.Set("Authorization", authorizationHeader("admin", "password")) router.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "admin", w.Body.String()) } @@ -112,7 +112,7 @@ func TestBasicAuth401(t *testing.T) { router.Use(BasicAuth(accounts)) router.GET("/login", func(c *Context) { called = true - c.String(200, c.MustGet(AuthUserKey).(string)) + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() @@ -121,7 +121,7 @@ func TestBasicAuth401(t *testing.T) { router.ServeHTTP(w, req) assert.False(t, called) - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "Basic realm=\"Authorization Required\"", w.HeaderMap.Get("WWW-Authenticate")) } @@ -132,7 +132,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { router.Use(BasicAuthForRealm(accounts, "My Custom \"Realm\"")) router.GET("/login", func(c *Context) { called = true - c.String(200, c.MustGet(AuthUserKey).(string)) + c.String(http.StatusOK, c.MustGet(AuthUserKey).(string)) }) w := httptest.NewRecorder() @@ -141,6 +141,6 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { router.ServeHTTP(w, req) assert.False(t, called) - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, 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 e797003..0b3f82d 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -54,7 +54,7 @@ func BenchmarkOneRouteJSON(B *testing.B) { Status string `json:"status"` }{"ok"} router.GET("/json", func(c *Context) { - c.JSON(200, data) + c.JSON(http.StatusOK, data) }) runRequest(B, router, "GET", "/json") } @@ -66,7 +66,7 @@ func BenchmarkOneRouteHTML(B *testing.B) { router.SetHTMLTemplate(t) router.GET("/html", func(c *Context) { - c.HTML(200, "index", "hola") + c.HTML(http.StatusOK, "index", "hola") }) runRequest(B, router, "GET", "/html") } @@ -82,7 +82,7 @@ func BenchmarkOneRouteSet(B *testing.B) { func BenchmarkOneRouteString(B *testing.B) { router := New() router.GET("/text", func(c *Context) { - c.String(200, "this is a plain text") + c.String(http.StatusOK, "this is a plain text") }) runRequest(B, router, "GET", "/text") } diff --git a/context_test.go b/context_test.go index 0185507..99d5267 100644 --- a/context_test.go +++ b/context_test.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "html/template" + "io" "mime/multipart" "net/http" "net/http/httptest" @@ -21,7 +22,6 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" - "io" ) var _ context.Context = &Context{} @@ -585,10 +585,11 @@ func TestContextGetCookie(t *testing.T) { } func TestContextBodyAllowedForStatus(t *testing.T) { + // todo(thinkerou): go1.6 not support StatusProcessing assert.False(t, false, bodyAllowedForStatus(102)) - assert.False(t, false, bodyAllowedForStatus(204)) - assert.False(t, false, bodyAllowedForStatus(304)) - assert.True(t, true, bodyAllowedForStatus(500)) + assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent)) + assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified)) + assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) } type TestPanicRender struct { @@ -619,9 +620,9 @@ func TestContextRenderJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.JSON(201, H{"foo": "bar"}) + c.JSON(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -633,9 +634,9 @@ func TestContextRenderJSONP(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) - c.JSONP(201, H{"foo": "bar"}) + c.JSONP(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -647,9 +648,9 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("GET", "http://example.com", nil) - c.JSONP(201, H{"foo": "bar"}) + c.JSONP(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -659,9 +660,9 @@ func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.JSON(204, H{"foo": "bar"}) + c.JSON(http.StatusNoContent, H{"foo": "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -673,9 +674,9 @@ func TestContextRenderAPIJSON(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "application/vnd.api+json") - c.JSON(201, H{"foo": "bar"}) + c.JSON(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type")) } @@ -686,9 +687,9 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "application/vnd.api+json") - c.JSON(204, H{"foo": "bar"}) + c.JSON(http.StatusNoContent, H{"foo": "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") } @@ -699,9 +700,9 @@ func TestContextRenderIndentedJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.IndentedJSON(201, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) + c.IndentedJSON(http.StatusCreated, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -711,9 +712,9 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.IndentedJSON(204, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) + c.IndentedJSON(http.StatusNoContent, H{"foo": "bar", "bar": "foo", "nested": H{"foo": "bar"}}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -725,9 +726,9 @@ func TestContextRenderSecureJSON(t *testing.T) { c, router := CreateTestContext(w) router.SecureJsonPrefix("&&&START&&&") - c.SecureJSON(201, []string{"foo", "bar"}) + c.SecureJSON(http.StatusCreated, []string{"foo", "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -737,9 +738,9 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.SecureJSON(204, []string{"foo", "bar"}) + c.SecureJSON(http.StatusNoContent, []string{"foo", "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -764,9 +765,9 @@ func TestContextRenderHTML(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) - c.HTML(201, "t", H{"name": "alexandernyquist"}) + c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -788,9 +789,9 @@ func TestContextRenderHTML2(t *testing.T) { assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String()) - c.HTML(201, "t", H{"name": "alexandernyquist"}) + c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -802,9 +803,9 @@ func TestContextRenderNoContentHTML(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) - c.HTML(204, "t", H{"name": "alexandernyquist"}) + c.HTML(http.StatusNoContent, "t", H{"name": "alexandernyquist"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -815,9 +816,9 @@ func TestContextRenderXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.XML(201, H{"foo": "bar"}) + c.XML(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -827,9 +828,9 @@ func TestContextRenderNoContentXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.XML(204, H{"foo": "bar"}) + c.XML(http.StatusNoContent, H{"foo": "bar"}) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -840,9 +841,9 @@ func TestContextRenderString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.String(201, "test %s %d", "string", 2) + c.String(http.StatusCreated, "test %s %d", "string", 2) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "test string 2", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -852,9 +853,9 @@ func TestContextRenderNoContentString(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.String(204, "test %s %d", "string", 2) + c.String(http.StatusNoContent, "test %s %d", "string", 2) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -866,9 +867,9 @@ func TestContextRenderHTMLString(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "text/html; charset=utf-8") - c.String(201, "%s %d", "string", 3) + c.String(http.StatusCreated, "%s %d", "string", 3) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "string 3", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -879,9 +880,9 @@ func TestContextRenderNoContentHTMLString(t *testing.T) { c, _ := CreateTestContext(w) c.Header("Content-Type", "text/html; charset=utf-8") - c.String(204, "%s %d", "string", 3) + c.String(http.StatusNoContent, "%s %d", "string", 3) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -892,9 +893,9 @@ func TestContextRenderData(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Data(201, "text/csv", []byte(`foo,bar`)) + c.Data(http.StatusCreated, "text/csv", []byte(`foo,bar`)) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo,bar", w.Body.String()) assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) } @@ -904,9 +905,9 @@ func TestContextRenderNoContentData(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Data(204, "text/csv", []byte(`foo,bar`)) + c.Data(http.StatusNoContent, "text/csv", []byte(`foo,bar`)) - assert.Equal(t, 204, w.Code) + assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) } @@ -935,7 +936,7 @@ func TestContextRenderFile(t *testing.T) { c.Request, _ = http.NewRequest("GET", "/", nil) c.File("./gin.go") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -946,9 +947,9 @@ func TestContextRenderYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.YAML(201, H{"foo": "bar"}) + c.YAML(http.StatusCreated, H{"foo": "bar"}) - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -978,9 +979,9 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) { assert.Panics(t, func() { c.Redirect(299, "/new_path") }) assert.Panics(t, func() { c.Redirect(309, "/new_path") }) - c.Redirect(301, "/path") + c.Redirect(http.StatusMovedPermanently, "/path") c.Writer.WriteHeaderNow() - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) assert.Equal(t, "/path", w.Header().Get("Location")) } @@ -989,10 +990,10 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) - c.Redirect(302, "http://google.com") + c.Redirect(http.StatusFound, "http://google.com") c.Writer.WriteHeaderNow() - assert.Equal(t, 302, w.Code) + assert.Equal(t, http.StatusFound, w.Code) assert.Equal(t, "http://google.com", w.Header().Get("Location")) } @@ -1001,21 +1002,23 @@ func TestContextRenderRedirectWith201(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) - c.Redirect(201, "/resource") + c.Redirect(http.StatusCreated, "/resource") c.Writer.WriteHeaderNow() - assert.Equal(t, 201, w.Code) + assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "/resource", w.Header().Get("Location")) } func TestContextRenderRedirectAll(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "http://example.com", nil) - assert.Panics(t, func() { c.Redirect(200, "/resource") }) - assert.Panics(t, func() { c.Redirect(202, "/resource") }) + assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") }) + assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") }) assert.Panics(t, func() { c.Redirect(299, "/resource") }) assert.Panics(t, func() { c.Redirect(309, "/resource") }) - assert.NotPanics(t, func() { c.Redirect(300, "/resource") }) + assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") }) + // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) + // when we upgrade go version we can use http.StatusPermanentRedirect assert.NotPanics(t, func() { c.Redirect(308, "/resource") }) } @@ -1024,12 +1027,12 @@ func TestContextNegotiationWithJSON(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEJSON, MIMEXML}, Data: H{"foo": "bar"}, }) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -1039,12 +1042,12 @@ func TestContextNegotiationWithXML(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEXML, MIMEJSON}, Data: H{"foo": "bar"}, }) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -1056,13 +1059,13 @@ func TestContextNegotiationWithHTML(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEHTML}, Data: H{"name": "gin"}, HTMLName: "t", }) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Hello gin", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -1072,11 +1075,11 @@ func TestContextNegotiationNotSupport(t *testing.T) { c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest("POST", "", nil) - c.Negotiate(200, Negotiate{ + c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEPOSTForm}, }) - assert.Equal(t, 406, w.Code) + assert.Equal(t, http.StatusNotAcceptable, w.Code) assert.Equal(t, c.index, abortIndex) assert.True(t, c.IsAborted()) } @@ -1134,11 +1137,11 @@ func TestContextAbortWithStatus(t *testing.T) { c, _ := CreateTestContext(w) c.index = 4 - c.AbortWithStatus(401) + c.AbortWithStatus(http.StatusUnauthorized) assert.Equal(t, abortIndex, c.index) - assert.Equal(t, 401, c.Writer.Status()) - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, c.Writer.Status()) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.True(t, c.IsAborted()) } @@ -1156,11 +1159,11 @@ func TestContextAbortWithStatusJSON(t *testing.T) { in.Bar = "barValue" in.Foo = "fooValue" - c.AbortWithStatusJSON(415, in) + c.AbortWithStatusJSON(http.StatusUnsupportedMediaType, in) assert.Equal(t, abortIndex, c.index) - assert.Equal(t, 415, c.Writer.Status()) - assert.Equal(t, 415, w.Code) + assert.Equal(t, http.StatusUnsupportedMediaType, c.Writer.Status()) + assert.Equal(t, http.StatusUnsupportedMediaType, w.Code) assert.True(t, c.IsAborted()) contentType := w.Header().Get("Content-Type") @@ -1223,9 +1226,9 @@ func TestContextAbortWithError(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.AbortWithError(401, errors.New("bad input")).SetMeta("some input") + c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, abortIndex, c.index) assert.True(t, c.IsAborted()) } @@ -1333,7 +1336,7 @@ func TestContextBadAutoBind(t *testing.T) { assert.Empty(t, obj.Bar) assert.Empty(t, obj.Foo) - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.True(t, c.IsAborted()) } diff --git a/examples/basic/main.go b/examples/basic/main.go index 473c6a0..48fa7bb 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -1,6 +1,8 @@ package main import ( + "net/http" + "github.com/gin-gonic/gin" ) @@ -13,7 +15,7 @@ func setupRouter() *gin.Engine { // Ping test r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) // Get user value @@ -21,9 +23,9 @@ func setupRouter() *gin.Engine { user := c.Params.ByName("name") value, ok := DB[user] if ok { - c.JSON(200, gin.H{"user": user, "value": value}) + c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) } else { - c.JSON(200, gin.H{"user": user, "status": "no value"}) + c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"}) } }) @@ -49,7 +51,7 @@ func setupRouter() *gin.Engine { if c.Bind(&json) == nil { DB[user] = json.Value - c.JSON(200, gin.H{"status": "ok"}) + c.JSON(http.StatusOK, gin.H{"status": "ok"}) } }) diff --git a/examples/basic/main_test.go b/examples/basic/main_test.go index 61203d6..5eb8524 100644 --- a/examples/basic/main_test.go +++ b/examples/basic/main_test.go @@ -15,6 +15,6 @@ func TestPingRoute(t *testing.T) { req, _ := http.NewRequest("GET", "/ping", nil) router.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "pong", w.Body.String()) } diff --git a/examples/favicon/main.go b/examples/favicon/main.go index 5ad3933..d32ca09 100644 --- a/examples/favicon/main.go +++ b/examples/favicon/main.go @@ -1,6 +1,8 @@ package main import ( + "net/http" + "github.com/gin-gonic/gin" "github.com/thinkerou/favicon" ) @@ -9,7 +11,7 @@ func main() { app := gin.Default() app.Use(favicon.New("./favicon.ico")) app.GET("/ping", func(c *gin.Context) { - c.String(200, "Hello favicon.") + c.String(http.StatusOK, "Hello favicon.") }) app.Run(":8080") } diff --git a/examples/http2/main.go b/examples/http2/main.go index 07df01e..6598a4c 100644 --- a/examples/http2/main.go +++ b/examples/http2/main.go @@ -3,6 +3,7 @@ package main import ( "html/template" "log" + "net/http" "os" "github.com/gin-gonic/gin" @@ -27,7 +28,7 @@ func main() { r.SetHTMLTemplate(html) r.GET("/welcome", func(c *gin.Context) { - c.HTML(200, "https", gin.H{ + c.HTML(http.StatusOK, "https", gin.H{ "status": "success", }) }) diff --git a/examples/realtime-advanced/routes.go b/examples/realtime-advanced/routes.go index 86da9be..03c6991 100644 --- a/examples/realtime-advanced/routes.go +++ b/examples/realtime-advanced/routes.go @@ -4,6 +4,7 @@ import ( "fmt" "html" "io" + "net/http" "strings" "time" @@ -21,12 +22,12 @@ func rateLimit(c *gin.Context) { fmt.Println("ip blocked") } c.Abort() - c.String(503, "you were automatically banned :)") + c.String(http.StatusServiceUnavailable, "you were automatically banned :)") } } func index(c *gin.Context) { - c.Redirect(301, "/room/hn") + c.Redirect(http.StatusMovedPermanently, "/room/hn") } func roomGET(c *gin.Context) { @@ -38,7 +39,7 @@ func roomGET(c *gin.Context) { if len(nick) > 13 { nick = nick[0:12] + "..." } - c.HTML(200, "room_login.templ.html", gin.H{ + c.HTML(http.StatusOK, "room_login.templ.html", gin.H{ "roomid": roomid, "nick": nick, "timestamp": time.Now().Unix(), @@ -55,7 +56,7 @@ func roomPOST(c *gin.Context) { validMessage := len(message) > 1 && len(message) < 200 validNick := len(nick) > 1 && len(nick) < 14 if !validMessage || !validNick { - c.JSON(400, gin.H{ + c.JSON(http.StatusBadRequest, gin.H{ "status": "failed", "error": "the message or nickname is too long", }) @@ -68,7 +69,7 @@ func roomPOST(c *gin.Context) { } messages.Add("inbound", 1) room(roomid).Submit(post) - c.JSON(200, post) + c.JSON(http.StatusOK, post) } func streamRoom(c *gin.Context) { diff --git a/examples/realtime-chat/main.go b/examples/realtime-chat/main.go index e4b55a0..5741fcb 100644 --- a/examples/realtime-chat/main.go +++ b/examples/realtime-chat/main.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "math/rand" + "net/http" "github.com/gin-gonic/gin" ) @@ -34,7 +35,7 @@ func stream(c *gin.Context) { func roomGET(c *gin.Context) { roomid := c.Param("roomid") userid := fmt.Sprint(rand.Int31()) - c.HTML(200, "chat_room", gin.H{ + c.HTML(http.StatusOK, "chat_room", gin.H{ "roomid": roomid, "userid": userid, }) @@ -46,7 +47,7 @@ func roomPOST(c *gin.Context) { message := c.PostForm("message") room(roomid).Submit(userid + ": " + message) - c.JSON(200, gin.H{ + c.JSON(http.StatusOK, gin.H{ "status": "success", "message": message, }) diff --git a/githubapi_test.go b/githubapi_test.go index a08c264..f631035 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -293,7 +293,7 @@ func githubConfigRouter(router *Engine) { for _, param := range c.Params { output[param.Key] = param.Value } - c.JSON(200, output) + c.JSON(http.StatusOK, output) }) } } diff --git a/logger.go b/logger.go index c679c78..1a8df60 100644 --- a/logger.go +++ b/logger.go @@ -7,6 +7,7 @@ package gin import ( "fmt" "io" + "net/http" "os" "time" @@ -118,11 +119,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { func colorForStatus(code int) string { switch { - case code >= 200 && code < 300: + case code >= http.StatusOK && code < http.StatusMultipleChoices: return green - case code >= 300 && code < 400: + case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: return white - case code >= 400 && code < 500: + case code >= http.StatusBadRequest && code < http.StatusInternalServerError: return yellow default: return red diff --git a/logger_test.go b/logger_test.go index 74a9659..7dbbf7b 100644 --- a/logger_test.go +++ b/logger_test.go @@ -7,6 +7,7 @@ package gin import ( "bytes" "errors" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -93,9 +94,9 @@ func TestColorForMethod(t *testing.T) { } func TestColorForStatus(t *testing.T) { - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(200), "2xx should be green") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(301), "3xx should be white") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(404), "4xx should be yellow") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") + assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") } @@ -106,23 +107,23 @@ func TestErrorLogger(t *testing.T) { c.Error(errors.New("this is an error")) }) router.GET("/abort", func(c *Context) { - c.AbortWithError(401, errors.New("no authorized")) + c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) }) router.GET("/print", func(c *Context) { c.Error(errors.New("this is an error")) - c.String(500, "hola!") + c.String(http.StatusInternalServerError, "hola!") }) w := performRequest(router, "GET", "/error") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) w = performRequest(router, "GET", "/abort") - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) w = performRequest(router, "GET", "/print") - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } diff --git a/middleware_test.go b/middleware_test.go index aa6a37a..983ad93 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -6,6 +6,7 @@ package gin import ( "errors" + "net/http" "strings" "testing" @@ -37,7 +38,7 @@ func TestMiddlewareGeneralCase(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "ACDB", signature) } @@ -73,7 +74,7 @@ func TestMiddlewareNoRoute(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, "ACEGHFDB", signature) } @@ -110,7 +111,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 405, w.Code) + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) assert.Equal(t, "ACEGHFDB", signature) } @@ -147,7 +148,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, "AC X DB", signature) } @@ -159,7 +160,7 @@ func TestMiddlewareAbort(t *testing.T) { }) router.Use(func(c *Context) { signature += "C" - c.AbortWithStatus(401) + c.AbortWithStatus(http.StatusUnauthorized) c.Next() signature += "D" }) @@ -173,7 +174,7 @@ func TestMiddlewareAbort(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 401, w.Code) + assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "ACD", signature) } @@ -183,7 +184,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { router.Use(func(c *Context) { signature += "A" c.Next() - c.AbortWithStatus(410) + c.AbortWithStatus(http.StatusGone) signature += "B" }) @@ -195,7 +196,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 410, w.Code) + assert.Equal(t, http.StatusGone, w.Code) assert.Equal(t, "ACB", signature) } @@ -207,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { router := New() router.Use(func(context *Context) { signature += "A" - context.AbortWithError(500, errors.New("foo")) + context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) }) router.Use(func(context *Context) { signature += "B" @@ -218,25 +219,25 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { w := performRequest(router, "GET", "/") // TEST - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "A", signature) } func TestMiddlewareWrite(t *testing.T) { router := New() router.Use(func(c *Context) { - c.String(400, "hola\n") + c.String(http.StatusBadRequest, "hola\n") }) router.Use(func(c *Context) { - c.XML(400, H{"foo": "bar"}) + c.XML(http.StatusBadRequest, H{"foo": "bar"}) }) router.Use(func(c *Context) { - c.JSON(400, H{"foo": "bar"}) + c.JSON(http.StatusBadRequest, H{"foo": "bar"}) }) router.GET("/", func(c *Context) { - c.JSON(400, H{"foo": "bar"}) + c.JSON(http.StatusBadRequest, H{"foo": "bar"}) }, func(c *Context) { - c.Render(400, sse.Event{ + c.Render(http.StatusBadRequest, sse.Event{ Event: "test", Data: "message", }) @@ -244,6 +245,6 @@ func TestMiddlewareWrite(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) } diff --git a/recovery_test.go b/recovery_test.go index fa065b1..53f4a07 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -6,6 +6,7 @@ package gin import ( "bytes" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -22,7 +23,7 @@ func TestPanicInHandler(t *testing.T) { // RUN w := performRequest(router, "GET", "/recovery") // TEST - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "TestPanicInHandler") @@ -33,13 +34,13 @@ func TestPanicWithAbort(t *testing.T) { router := New() router.Use(RecoveryWithWriter(nil)) router.GET("/recovery", func(c *Context) { - c.AbortWithStatus(400) + c.AbortWithStatus(http.StatusBadRequest) panic("Oupps, Houston, we have a problem") }) // RUN w := performRequest(router, "GET", "/recovery") // TEST - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) } func TestSource(t *testing.T) { diff --git a/render/redirect.go b/render/redirect.go index f874a35..a0634f5 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -16,6 +16,8 @@ type Redirect struct { } func (r Redirect) Render(w http.ResponseWriter) error { + // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) + // when we upgrade go version we can use http.StatusPermanentRedirect if (r.Code < 300 || r.Code > 308) && r.Code != 201 { panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code)) } diff --git a/render/render_test.go b/render/render_test.go index 8fad12b..47abf26 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -286,7 +286,7 @@ func TestRenderRedirect(t *testing.T) { assert.NoError(t, err) data1 := Redirect{ - Code: 301, + Code: http.StatusMovedPermanently, Request: req, Location: "/new/location", } @@ -296,7 +296,7 @@ func TestRenderRedirect(t *testing.T) { assert.NoError(t, err) data2 := Redirect{ - Code: 200, + Code: http.StatusOK, Request: req, Location: "/new/location", } diff --git a/response_writer_test.go b/response_writer_test.go index fcc48b3..cc5a89d 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -35,10 +35,10 @@ func TestResponseWriterReset(t *testing.T) { writer.reset(testWritter) assert.Equal(t, -1, writer.size) - assert.Equal(t, 200, writer.status) + assert.Equal(t, http.StatusOK, writer.status) assert.Equal(t, testWritter, writer.ResponseWriter) assert.Equal(t, -1, w.Size()) - assert.Equal(t, 200, w.Status()) + assert.Equal(t, http.StatusOK, w.Status()) assert.False(t, w.Written()) } @@ -48,13 +48,13 @@ func TestResponseWriterWriteHeader(t *testing.T) { writer.reset(testWritter) w := ResponseWriter(writer) - w.WriteHeader(300) + w.WriteHeader(http.StatusMultipleChoices) assert.False(t, w.Written()) - assert.Equal(t, 300, w.Status()) - assert.NotEqual(t, 300, testWritter.Code) + assert.Equal(t, http.StatusMultipleChoices, w.Status()) + assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code) w.WriteHeader(-1) - assert.Equal(t, 300, w.Status()) + assert.Equal(t, http.StatusMultipleChoices, w.Status()) } func TestResponseWriterWriteHeadersNow(t *testing.T) { @@ -63,12 +63,12 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) { writer.reset(testWritter) w := ResponseWriter(writer) - w.WriteHeader(300) + w.WriteHeader(http.StatusMultipleChoices) w.WriteHeaderNow() assert.True(t, w.Written()) assert.Equal(t, 0, w.Size()) - assert.Equal(t, 300, testWritter.Code) + assert.Equal(t, http.StatusMultipleChoices, testWritter.Code) writer.size = 10 w.WriteHeaderNow() @@ -84,8 +84,8 @@ func TestResponseWriterWrite(t *testing.T) { n, err := w.Write([]byte("hola")) assert.Equal(t, 4, n) assert.Equal(t, 4, w.Size()) - assert.Equal(t, 200, w.Status()) - assert.Equal(t, 200, testWritter.Code) + assert.Equal(t, http.StatusOK, w.Status()) + assert.Equal(t, http.StatusOK, testWritter.Code) assert.Equal(t, "hola", testWritter.Body.String()) assert.NoError(t, err) diff --git a/routergroup_test.go b/routergroup_test.go index a362e23..ce3d54a 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -5,6 +5,7 @@ package gin import ( + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -50,7 +51,7 @@ func performRequestInGroup(t *testing.T, method string) { assert.Equal(t, "/v1/login/", login.BasePath()) handler := func(c *Context) { - c.String(400, "the method was %s and index %d", c.Request.Method, c.index) + c.String(http.StatusBadRequest, "the method was %s and index %d", c.Request.Method, c.index) } switch method { @@ -80,11 +81,11 @@ func performRequestInGroup(t *testing.T, method string) { } w := performRequest(router, method, "/v1/login/test") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 3", w.Body.String()) w = performRequest(router, method, "/v1/test") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 1", w.Body.String()) } diff --git a/routes_test.go b/routes_test.go index a2389f9..23e749e 100644 --- a/routes_test.go +++ b/routes_test.go @@ -80,20 +80,20 @@ func testRouteNotOK2(method string, t *testing.T) { func TestRouterMethod(t *testing.T) { router := New() router.PUT("/hey2", func(c *Context) { - c.String(200, "sup2") + c.String(http.StatusOK, "sup2") }) router.PUT("/hey", func(c *Context) { - c.String(200, "called") + c.String(http.StatusOK, "called") }) router.PUT("/hey3", func(c *Context) { - c.String(200, "sup3") + c.String(http.StatusOK, "sup3") }) w := performRequest(router, "PUT", "/hey") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "called", w.Body.String()) } @@ -144,42 +144,42 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w := performRequest(router, "GET", "/path/") assert.Equal(t, "/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "GET", "/path2") assert.Equal(t, "/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "POST", "/path3/") assert.Equal(t, "/path3", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "PUT", "/path4") assert.Equal(t, "/path4/", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "GET", "/path") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "GET", "/path2/") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "POST", "/path3") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = performRequest(router, "PUT", "/path4/") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) router.RedirectTrailingSlash = false w = performRequest(router, "GET", "/path/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "GET", "/path2") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "POST", "/path3/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) w = performRequest(router, "PUT", "/path4") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouteRedirectFixedPath(t *testing.T) { @@ -194,19 +194,19 @@ func TestRouteRedirectFixedPath(t *testing.T) { w := performRequest(router, "GET", "/PATH") assert.Equal(t, "/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "GET", "/path2") assert.Equal(t, "/Path2", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = performRequest(router, "POST", "/path3") assert.Equal(t, "/PATH3", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) w = performRequest(router, "POST", "/path4") assert.Equal(t, "/Path4/", w.Header().Get("Location")) - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) } // TestContextParamsGet tests that a parameter can be parsed from the URL. @@ -236,7 +236,7 @@ func TestRouteParamsByName(t *testing.T) { w := performRequest(router, "GET", "/test/john/smith/is/super/great") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "john", name) assert.Equal(t, "smith", lastName) assert.Equal(t, "/is/super/great", wild) @@ -265,7 +265,7 @@ func TestRouteStaticFile(t *testing.T) { w2 := performRequest(router, "GET", "/result") assert.Equal(t, w, w2) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Gin Web Framework", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) @@ -273,7 +273,7 @@ func TestRouteStaticFile(t *testing.T) { w4 := performRequest(router, "HEAD", "/result") assert.Equal(t, w3, w4) - assert.Equal(t, 200, w3.Code) + assert.Equal(t, http.StatusOK, w3.Code) } // TestHandleStaticDir - ensure the root/sub dir handles properly @@ -283,7 +283,7 @@ func TestRouteStaticListingDir(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "gin.go") assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) } @@ -295,7 +295,7 @@ func TestRouteStaticNoListing(t *testing.T) { w := performRequest(router, "GET", "/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.NotContains(t, w.Body.String(), "gin.go") } @@ -310,7 +310,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { w := performRequest(router, "GET", "/gin.go") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") @@ -348,14 +348,14 @@ func TestRouteNotAllowedDisabled(t *testing.T) { router.HandleMethodNotAllowed = false router.POST("/path", func(c *Context) {}) w := performRequest(router, "GET", "/path") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) w = performRequest(router, "GET", "/path") assert.Equal(t, "404 page not found", w.Body.String()) - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouterNotFound(t *testing.T) { @@ -370,20 +370,20 @@ func TestRouterNotFound(t *testing.T) { code int location string }{ - {"/path/", 301, "/path"}, // TSR -/ - {"/dir", 301, "/dir/"}, // TSR +/ - {"", 301, "/"}, // TSR +/ - {"/PATH", 301, "/path"}, // Fixed Case - {"/DIR/", 301, "/dir/"}, // Fixed Case - {"/PATH/", 301, "/path"}, // Fixed Case -/ - {"/DIR", 301, "/dir/"}, // Fixed Case +/ - {"/../path", 301, "/path"}, // CleanPath - {"/nope", 404, ""}, // NotFound + {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ + {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ + {"", http.StatusMovedPermanently, "/"}, // TSR +/ + {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case + {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case + {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ + {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ + {"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath + {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) assert.Equal(t, tr.code, w.Code) - if w.Code != 404 { + if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) } } @@ -391,24 +391,24 @@ func TestRouterNotFound(t *testing.T) { // Test custom not found handler var notFound bool router.NoRoute(func(c *Context) { - c.AbortWithStatus(404) + c.AbortWithStatus(http.StatusNotFound) notFound = true }) w := performRequest(router, "GET", "/nope") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) assert.True(t, notFound) // Test other method than GET (want 307 instead of 301) router.PATCH("/path", func(c *Context) {}) w = performRequest(router, "PATCH", "/path/") - assert.Equal(t, 307, w.Code) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header())) // Test special case where no node for the prefix "/" exists router = New() router.GET("/a", func(c *Context) {}) w = performRequest(router, "GET", "/") - assert.Equal(t, 404, w.Code) + assert.Equal(t, http.StatusNotFound, w.Code) } func TestRouteRawPath(t *testing.T) { @@ -427,7 +427,7 @@ func TestRouteRawPath(t *testing.T) { }) w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) } func TestRouteRawPathNoUnescape(t *testing.T) { @@ -447,7 +447,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) { }) w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333") - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) } func TestRouteServeErrorWithWriteHeader(t *testing.T) { diff --git a/utils_test.go b/utils_test.go index 95b5de5..9b57c57 100644 --- a/utils_test.go +++ b/utils_test.go @@ -25,7 +25,7 @@ type testStruct struct { func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { assert.Equal(t.T, "POST", req.Method) assert.Equal(t.T, "/path", req.URL.Path) - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, "hello") } @@ -35,16 +35,16 @@ func TestWrap(t *testing.T) { router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) { assert.Equal(t, "GET", req.Method) assert.Equal(t, "/path2", req.URL.Path) - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "hola!") })) w := performRequest(router, "POST", "/path") - assert.Equal(t, 500, w.Code) + assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hello", w.Body.String()) w = performRequest(router, "GET", "/path2") - assert.Equal(t, 400, w.Code) + assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "hola!", w.Body.String()) } From 6c8a9731345b6782c923054b0c973c2b4b9394a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 14 Aug 2018 11:35:13 +0800 Subject: [PATCH 193/912] add issue and pull request template explain (#1483) * add issue/pr template explain * add issue/pr template explain --- .github/ISSUE_TEMPLATE.md | 13 +++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 7 +++++++ 2 files changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..de067ce --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +- With issues: + - Use the search tool before opening a new issue. + - Please provide source code and commit sha if you found a bug. + - Review existing issues and provide feedback or react to them. + +- gin version (or commit ref): +- git version: +- operating system: + +## Description + +## Screenshots + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..8630bc3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +- With pull requests: + - Open your pull request against `master` + - Your pull request should have no more than two commits, if not you should squash them. + - It should pass all tests in the available continuous integrations systems such as TravisCI. + - You should add/modify tests to cover your proposed code changes. + - If your pull request contains a new feature, please document it on the README. + From b869fe1415e4b9eb52f247441830d502aece2d4d Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Tue, 14 Aug 2018 10:58:52 +0200 Subject: [PATCH 194/912] docs: add changelog for v1.3.0, update authors and version const (#1478) * docs: add changelog for v1.3.0, update authors and version const * add link for every referenced pull request (#1481) * docs: add changelog for v1.3.0, update authors and version const * add link for pr --- AUTHORS.md | 6 +++++- CHANGELOG.md | 24 +++++++++++++++++++++++- gin.go | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 7ab7213..dda19bc 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -1,8 +1,12 @@ List of all the awesome people working to make Gin the best Web Framework in Go. +## gin 1.x series authors + +**Gin Core Team:** Bo-Yi Wu (@appleboy), 田欧 (@thinkerou), Javier Provecho (@javierprovecho) + ## gin 0.x series authors -**Maintainer:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) +**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) People and companies, who have contributed, in alphabetical order. diff --git a/CHANGELOG.md b/CHANGELOG.md index ee485ec..e6a108c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ # CHANGELOG -### Gin 1.2 +### Gin 1.3.0 + +- [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383) +- [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358) +- [NEW] Add `Pusher()` in [`type ResponseWriter`](https://godoc.org/github.com/gin-gonic/gin#ResponseWriter) for supporting http2 push, see [#1273](https://github.com/gin-gonic/gin/pull/1273) +- [NEW] Add [`func (*Context) DataFromReader`](https://godoc.org/github.com/gin-gonic/gin#Context.DataFromReader) for serving dynamic data, see [#1304](https://github.com/gin-gonic/gin/pull/1304) +- [NEW] Add [`func (*Context) ShouldBindBodyWith`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindBodyWith) allowing to call binding multiple times, see [#1341](https://github.com/gin-gonic/gin/pull/1341) +- [NEW] Support pointers in form binding, see [#1336](https://github.com/gin-gonic/gin/pull/1336) +- [NEW] Add [`func (*Context) JSONP`](https://godoc.org/github.com/gin-gonic/gin#Context.JSONP), see [#1333](https://github.com/gin-gonic/gin/pull/1333) +- [NEW] Support default value in form binding, see [#1138](https://github.com/gin-gonic/gin/pull/1138) +- [NEW] Expose validator engine in [`type StructValidator`](https://godoc.org/github.com/gin-gonic/gin/binding#StructValidator), see [#1277](https://github.com/gin-gonic/gin/pull/1277) +- [NEW] Add [`func (*Context) ShouldBind`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBind), [`func (*Context) ShouldBindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindQuery) and [`func (*Context) ShouldBindJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindJSON), see [#1047](https://github.com/gin-gonic/gin/pull/1047) +- [NEW] Add support for `time.Time` location in form binding, see [#1117](https://github.com/gin-gonic/gin/pull/1117) +- [NEW] Add [`func (*Context) BindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.BindQuery), see [#1029](https://github.com/gin-gonic/gin/pull/1029) +- [NEW] Make [jsonite](https://github.com/json-iterator/go) optional with build tags, see [#1026](https://github.com/gin-gonic/gin/pull/1026) +- [NEW] Show query string in logger, see [#999](https://github.com/gin-gonic/gin/pull/999) +- [NEW] Add [`func (*Context) SecureJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.SecureJSON), see [#987](https://github.com/gin-gonic/gin/pull/987) and [#993](https://github.com/gin-gonic/gin/pull/993) +- [DEPRECATE] `func (*Context) GetCookie` for [`func (*Context) Cookie`](https://godoc.org/github.com/gin-gonic/gin#Context.Cookie) +- [FIX] Don't display color tags if [`func DisableConsoleColor`](https://godoc.org/github.com/gin-gonic/gin#DisableConsoleColor) called, see [#1072](https://github.com/gin-gonic/gin/pull/1072) +- [FIX] Gin Mode `""` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250) +- [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460) + +### Gin 1.2.0 - [NEW] Switch from godeps to govendor - [NEW] Add support for Let's Encrypt via gin-gonic/autotls diff --git a/gin.go b/gin.go index fc4d656..aa62e01 100644 --- a/gin.go +++ b/gin.go @@ -16,7 +16,7 @@ import ( const ( // Version is Framework's version. - Version = "v1.2" + Version = "v1.3.0" defaultMultipartMemory = 32 << 20 // 32 MB ) From 64a45486421c37bd60af66ce96a3ef7b62a191aa Mon Sep 17 00:00:00 2001 From: Abner Chen Date: Wed, 15 Aug 2018 13:42:12 +0800 Subject: [PATCH 195/912] Fix typo in readme (#1490) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28598ba..d8e3ffa 100644 --- a/README.md +++ b/README.md @@ -679,7 +679,7 @@ $ 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. +[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more. ### Only Bind Query String From bef6c56c8928cfe1d96a9fdefe29fcfdeb2b2f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 16 Aug 2018 17:38:17 +0800 Subject: [PATCH 196/912] chore: upgrade dependency library version (#1491) upgrade lib version, and upgrade `github.com/json-iterator/go` to add two libs. --- vendor/vendor.json | 92 +++++++++++++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index c34c2de..e6d038a 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1,13 +1,12 @@ { - "comment": "v1.2", + "comment": "v1.3.0", "ignore": "test", "package": [ { - "checksumSHA1": "dvabztWVQX8f6oMLRyv4dLH+TGY=", - "comment": "v1.1.0", + "checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=", "path": "github.com/davecgh/go-spew/spew", - "revision": "346938d642f2ec3594ed81d874461961cd0faa76", - "revisionTime": "2016-10-29T20:57:26Z" + "revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73", + "revisionTime": "2018-02-21T22:46:20Z" }, { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", @@ -16,35 +15,62 @@ "revisionTime": "2017-01-09T09:34:21Z" }, { - "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", + "checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=", "path": "github.com/golang/protobuf/proto", - "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", - "revisionTime": "2017-06-01T23:02:30Z" + "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", + "revisionTime": "2018-04-30T18:52:41Z", + "version": "v1.1.0", + "versionExact": "v1.1.0" }, { - "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", + "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=", "path": "github.com/json-iterator/go", - "revision": "36b14963da70d11297d313183d7e6388c8510e1e", - "revisionTime": "2017-08-29T15:58:51Z" + "revision": "1624edc4454b8682399def8740d46db5e4362ba4", + "revisionTime": "2018-08-06T06:07:27Z", + "version": "1.1.5", + "versionExact": "1.1.5" }, { - "checksumSHA1": "U6lX43KDDlNOn+Z0Yyww+ZzHfFo=", + "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=", "path": "github.com/mattn/go-isatty", - "revision": "57fdcb988a5c543893cc61bce354a6e24ab70022", - "revisionTime": "2017-03-07T16:30:44Z" + "revision": "0360b2af4f38e8d38c7fce2a9f4e702702d73a39", + "revisionTime": "2017-09-25T05:34:41Z", + "version": "v0.0.3", + "versionExact": "v0.0.3" }, { - "checksumSHA1": "Q2V7Zs3diLmLfmfbiuLpSxETSuY=", - "comment": "v1.1.4", + "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", + "path": "github.com/modern-go/concurrent", + "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", + "revisionTime": "2018-03-06T01:26:44Z" + }, + { + "checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=", + "path": "github.com/modern-go/reflect2", + "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", + "revisionTime": "2018-07-18T01:23:57Z" + }, + { + "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", + "path": "github.com/pmezard/go-difflib/difflib", + "revision": "792786c7400a136282c1664665ae0a8db921c6c2", + "revisionTime": "2016-01-10T10:55:54Z" + }, + { + "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", - "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", - "revisionTime": "2016-09-25T22:06:09Z" + "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", + "revisionTime": "2018-05-06T18:05:49Z", + "version": "v1.2.2", + "versionExact": "v1.2.2" }, { - "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", + "checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=", "path": "github.com/ugorji/go/codec", - "revision": "c88ee250d0221a57af388746f5cf03768c21d6e2", - "revisionTime": "2017-02-15T20:11:44Z" + "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab", + "revisionTime": "2018-04-07T10:07:33Z", + "version": "v1.1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", @@ -54,18 +80,26 @@ "revisionTime": "2016-10-18T08:54:36Z" }, { - "checksumSHA1": "39V1idWER42Lmcmg2Uy40wMzOlo=", - "comment": "v8.18.1", + "checksumSHA1": "7Gocawl8bm27cpAILtuf21xvVD8=", + "path": "golang.org/x/sys/unix", + "revision": "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded", + "revisionTime": "2018-08-15T07:37:39Z" + }, + { + "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", "path": "gopkg.in/go-playground/validator.v8", - "revision": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c", - "revisionTime": "2016-07-18T13:41:25Z" + "revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf", + "revisionTime": "2017-07-30T05:02:35Z", + "version": "v8.18.2", + "versionExact": "v8.18.2" }, { - "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", - "comment": "v2", + "checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=", "path": "gopkg.in/yaml.v2", - "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", - "revisionTime": "2016-09-28T15:37:09Z" + "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", + "revisionTime": "2018-03-28T19:50:20Z", + "version": "v2.2.1", + "versionExact": "v2.2.1" } ], "rootPath": "github.com/gin-gonic/gin" From 40ab9de4b57ac6ce467a96b1fb4c3379658b29c2 Mon Sep 17 00:00:00 2001 From: syssam Date: Fri, 17 Aug 2018 09:12:15 +0800 Subject: [PATCH 197/912] Add BindXML AND ShouldBindXML #1484 (#1485) Add BindXML AND ShouldBindXML #1484 --- README.md | 61 ++++++++++++++++++++++++++++++++++--------------- context.go | 10 ++++++++ context_test.go | 41 +++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d8e3ffa..5de22dc 100644 --- a/README.md +++ b/README.md @@ -534,10 +534,10 @@ Note that you need to set the corresponding binding tag on all fields you want t Also, Gin provides two sets of methods for binding: - **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindQuery` + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `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` + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery` - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. @@ -547,8 +547,8 @@ You can also specify that specific fields are required. If a field is decorated ```go // Binding from JSON type Login struct { - User string `form:"user" json:"user" binding:"required"` - Password string `form:"password" json:"password" binding:"required"` + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` } func main() { @@ -557,30 +557,55 @@ func main() { // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login - if err := c.ShouldBindJSON(&json); err == nil { - if json.User == "manu" && json.Password == "123" { - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - } else { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - } - } else { + if err := c.ShouldBindXML(&json); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding XML ( + // + // + // user + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Example for binding a HTML form (user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. - if err := c.ShouldBind(&form); err == nil { - 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 { + if err := c.ShouldBind(&form); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) // Listen and serve on 0.0.0.0:8080 diff --git a/context.go b/context.go index 724ded7..bbdb7e4 100644 --- a/context.go +++ b/context.go @@ -511,6 +511,11 @@ func (c *Context) BindJSON(obj interface{}) error { return c.MustBindWith(obj, binding.JSON) } +// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML). +func (c *Context) BindXML(obj interface{}) error { + return c.MustBindWith(obj, binding.XML) +} + // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). func (c *Context) BindQuery(obj interface{}) error { return c.MustBindWith(obj, binding.Query) @@ -545,6 +550,11 @@ func (c *Context) ShouldBindJSON(obj interface{}) error { return c.ShouldBindWith(obj, binding.JSON) } +// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). +func (c *Context) ShouldBindXML(obj interface{}) error { + return c.ShouldBindWith(obj, binding.XML) +} + // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). func (c *Context) ShouldBindQuery(obj interface{}) error { return c.ShouldBindWith(obj, binding.Query) diff --git a/context_test.go b/context_test.go index 99d5267..13fb909 100644 --- a/context_test.go +++ b/context_test.go @@ -1302,6 +1302,26 @@ func TestContextBindWithJSON(t *testing.T) { assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } +func TestContextBindWithXML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + + FOO + BAR + `)) + c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type + + var obj struct { + Foo string `xml:"foo"` + Bar string `xml:"bar"` + } + assert.NoError(t, c.BindXML(&obj)) + assert.Equal(t, "FOO", obj.Foo) + assert.Equal(t, "BAR", obj.Bar) + assert.Equal(t, 0, w.Body.Len()) +} func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() @@ -1372,6 +1392,27 @@ func TestContextShouldBindWithJSON(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextShouldBindWithXML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + + FOO + BAR + `)) + c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type + + var obj struct { + Foo string `xml:"foo"` + Bar string `xml:"bar"` + } + assert.NoError(t, c.ShouldBindXML(&obj)) + assert.Equal(t, "FOO", obj.Foo) + assert.Equal(t, "BAR", obj.Bar) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From 7eb0f74b89af91b609f910f549e312a5c5db2ada Mon Sep 17 00:00:00 2001 From: Alexander Lokhman Date: Fri, 17 Aug 2018 02:41:56 +0100 Subject: [PATCH 198/912] Set default time format in form binding (#1487) --- binding/form_mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 3f6b9bf..a714291 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -178,7 +178,7 @@ func setFloatField(val string, bitSize int, field reflect.Value) error { func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { timeFormat := structField.Tag.Get("time_format") if timeFormat == "" { - return errors.New("Blank time format") + timeFormat = time.RFC3339 } if val == "" { From a643d206054d630f66fce6513db80d864159030a Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Fri, 17 Aug 2018 11:21:14 +0800 Subject: [PATCH 199/912] readme: fix users link (#1493) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5de22dc..37eec61 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [http2 server push](#http2-server-push) - [Testing](#testing) -- [Users](#users--) +- [Users](#users) ## Installation From f5451bd645be353ba40cd5d308a545f201e68283 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 17 Aug 2018 11:33:23 +0800 Subject: [PATCH 200/912] Fix typo in README [ci skip] (#1492) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37eec61..920b634 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 ## 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. +Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. ```sh $ go build -tags=jsoniter . From f856aa85cd175ad10d2f55b0a41a51a08f4bdb02 Mon Sep 17 00:00:00 2001 From: chainhelen Date: Fri, 17 Aug 2018 14:59:55 +0800 Subject: [PATCH 201/912] Update readme about the version of gin (#1494) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 920b634..161ea28 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" ```sh $ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.2 +$ govendor fetch github.com/gin-gonic/gin@v1.3 ``` 4. Copy a starting template inside your project From efdd3c8b81a42e1769fe70483d4828d5bba7e87e Mon Sep 17 00:00:00 2001 From: aljun Date: Sun, 19 Aug 2018 10:45:56 +0800 Subject: [PATCH 202/912] Add support for Protobuf format response and unit test (#1479) `Gin` now have the `protobufBinding` function to check the request format, but didn't have a protobuf response function like `c.YAML()`. In our company [ByteDance](http://bytedance.com/), the largest internet company using golang in China, we use `gin` to transfer __Protobuf__ instead of __Json__, we have to write some internal library to make some wrappers to achieve that, and the code is not elegant. So we really want such a feature. --- README.md | 17 +++++++++++++++-- context.go | 6 ++++++ context_test.go | 27 +++++++++++++++++++++++++++ render/protobuf.go | 33 +++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 31 +++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 render/protobuf.go diff --git a/README.md b/README.md index 161ea28..ae183f9 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [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) + - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) - [JSONP rendering](#jsonp) - [Serving static files](#serving-static-files) - [Serving data from reader](#serving-data-from-reader) @@ -871,7 +871,7 @@ Test it with: $ curl -v --form user=user --form password=password http://localhost:8080/login ``` -### XML, JSON and YAML rendering +### XML, JSON, YAML and ProtoBuf rendering ```go func main() { @@ -905,6 +905,19 @@ func main() { c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) + r.GET("/someProtoBuf", func(c *gin.Context) { + reps := []int64{int64(1), int64(2)} + label := "test" + // The specific definition of protobuf is written in the testdata/protoexample file. + data := &protoexample.Test{ + Label: &label, + Reps: reps, + } + // Note that data becomes binary data in the response + // Will output protoexample.Test protobuf serialized data + c.ProtoBuf(http.StatusOK, data) + }) + // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } diff --git a/context.go b/context.go index bbdb7e4..5cd5283 100644 --- a/context.go +++ b/context.go @@ -20,6 +20,7 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" + "github.com/golang/protobuf/proto" ) // Content-Type MIME of the most common data formats. @@ -845,6 +846,11 @@ func (c *Context) Stream(step func(w io.Writer) bool) { } } +// ProtoBuf serializes the given struct as ProtoBuf into the response body. +func (c *Context) ProtoBuf(code int, obj proto.Message) { + c.Render(code, render.ProtoBuf{Data: obj}) +} + /************************************/ /******** CONTENT NEGOTIATION *******/ /************************************/ diff --git a/context_test.go b/context_test.go index 13fb909..675c2a4 100644 --- a/context_test.go +++ b/context_test.go @@ -20,8 +20,11 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "golang.org/x/net/context" + + testdata "github.com/gin-gonic/gin/testdata/protoexample" ) var _ context.Context = &Context{} @@ -954,6 +957,30 @@ func TestContextRenderYAML(t *testing.T) { assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf +// and Content-Type is set to application/x-protobuf +// and we just use the example protobuf to check if the response is correct +func TestContextRenderProtoBuf(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + reps := []int64{int64(1), int64(2)} + label := "test" + data := &testdata.Test{ + Label: &label, + Reps: reps, + } + + c.ProtoBuf(http.StatusCreated, data) + + protoData, err := proto.Marshal(data) + assert.NoError(t, err) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type")) +} + func TestContextHeaders(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Header("Content-Type", "text/plain") diff --git a/render/protobuf.go b/render/protobuf.go new file mode 100644 index 0000000..fe82688 --- /dev/null +++ b/render/protobuf.go @@ -0,0 +1,33 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package render + +import ( + "net/http" + + "github.com/golang/protobuf/proto" +) + +type ProtoBuf struct { + Data proto.Message +} + +var protobufContentType = []string{"application/x-protobuf"} + +func (r ProtoBuf) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + + bytes, err := proto.Marshal(r.Data) + if err != nil { + return err + } + + w.Write(bytes) + return nil +} + +func (r ProtoBuf) WriteContentType(w http.ResponseWriter) { + writeContentType(w, protobufContentType) +} diff --git a/render/render.go b/render/render.go index 4ff1c7b..df0d1d7 100755 --- a/render/render.go +++ b/render/render.go @@ -27,6 +27,7 @@ var ( _ Render = MsgPack{} _ Render = Reader{} _ Render = AsciiJSON{} + _ Render = ProtoBuf{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_test.go b/render/render_test.go index 47abf26..905b76a 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,6 +15,8 @@ import ( "strings" "testing" + testdata "github.com/gin-gonic/gin/testdata/protoexample" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" ) @@ -265,6 +267,35 @@ func TestRenderYAMLFail(t *testing.T) { assert.Error(t, err) } +// test Protobuf rendering +func TestRenderProtoBuf(t *testing.T) { + w := httptest.NewRecorder() + reps := []int64{int64(1), int64(2)} + label := "test" + data := &testdata.Test{ + Label: &label, + Reps: reps, + } + + (ProtoBuf{data}).WriteContentType(w) + protoData, err := proto.Marshal(data) + assert.NoError(t, err) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) + + err = (ProtoBuf{data}).Render(w) + + assert.NoError(t, err) + assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) +} + +func TestRenderProtoBufFail(t *testing.T) { + w := httptest.NewRecorder() + data := &testdata.Test{} + err := (ProtoBuf{data}).Render(w) + assert.Error(t, err) +} + func TestRenderXML(t *testing.T) { w := httptest.NewRecorder() data := xmlmap{ From 6073a79ee08657ea9784c26b8521a8f8ef9e1644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 19 Aug 2018 17:39:58 +0800 Subject: [PATCH 203/912] not use protobuf on context but use it on render (#1496) --- context.go | 11 +++++------ render/json.go | 0 render/protobuf.go | 4 ++-- render/render.go | 0 render/render_test.go | 3 ++- 5 files changed, 9 insertions(+), 9 deletions(-) mode change 100755 => 100644 render/json.go mode change 100755 => 100644 render/render.go mode change 100755 => 100644 render/render_test.go diff --git a/context.go b/context.go index 5cd5283..6d80284 100644 --- a/context.go +++ b/context.go @@ -20,7 +20,6 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" - "github.com/golang/protobuf/proto" ) // Content-Type MIME of the most common data formats. @@ -784,6 +783,11 @@ func (c *Context) YAML(code int, obj interface{}) { c.Render(code, render.YAML{Data: obj}) } +// ProtoBuf serializes the given struct as ProtoBuf into the response body. +func (c *Context) ProtoBuf(code int, obj interface{}) { + c.Render(code, render.ProtoBuf{Data: obj}) +} + // String writes the given string into the response body. func (c *Context) String(code int, format string, values ...interface{}) { c.Render(code, render.String{Format: format, Data: values}) @@ -846,11 +850,6 @@ func (c *Context) Stream(step func(w io.Writer) bool) { } } -// ProtoBuf serializes the given struct as ProtoBuf into the response body. -func (c *Context) ProtoBuf(code int, obj proto.Message) { - c.Render(code, render.ProtoBuf{Data: obj}) -} - /************************************/ /******** CONTENT NEGOTIATION *******/ /************************************/ diff --git a/render/json.go b/render/json.go old mode 100755 new mode 100644 diff --git a/render/protobuf.go b/render/protobuf.go index fe82688..34f1e9b 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -11,7 +11,7 @@ import ( ) type ProtoBuf struct { - Data proto.Message + Data interface{} } var protobufContentType = []string{"application/x-protobuf"} @@ -19,7 +19,7 @@ var protobufContentType = []string{"application/x-protobuf"} func (r ProtoBuf) Render(w http.ResponseWriter) error { r.WriteContentType(w) - bytes, err := proto.Marshal(r.Data) + bytes, err := proto.Marshal(r.Data.(proto.Message)) if err != nil { return err } diff --git a/render/render.go b/render/render.go old mode 100755 new mode 100644 diff --git a/render/render_test.go b/render/render_test.go old mode 100755 new mode 100644 index 905b76a..cd20d01 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,10 +15,11 @@ import ( "strings" "testing" - testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/ugorji/go/codec" + + testdata "github.com/gin-gonic/gin/testdata/protoexample" ) // TODO unit tests From 32b58e0fd2b101cc0bedc925ac71f1bbca620869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 19 Aug 2018 22:14:02 +0800 Subject: [PATCH 204/912] render: update msgpack usage (#1498) please see msgpack usage: https://github.com/ugorji/go/tree/master/codec#usage --- render/msgpack.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/msgpack.go b/render/msgpack.go index e6c13e5..b6b8aa0 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -26,6 +26,6 @@ func (r MsgPack) Render(w http.ResponseWriter) error { func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { writeContentType(w, msgpackContentType) - var h codec.Handle = new(codec.MsgpackHandle) - return codec.NewEncoder(w, h).Encode(obj) + var mh codec.MsgpackHandle + return codec.NewEncoder(w, &mh).Encode(obj) } From b7bb9baa642d2b9d5918a5bcd3829dae64c65257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 19 Aug 2018 22:52:43 +0800 Subject: [PATCH 205/912] chore: add missing copyright and update if/else (#1497) --- render/reader.go | 4 ++++ render/text.go | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/render/reader.go b/render/reader.go index be2132c..7a06cce 100644 --- a/render/reader.go +++ b/render/reader.go @@ -1,3 +1,7 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package render import ( diff --git a/render/text.go b/render/text.go index 74cd26b..7a6acc4 100644 --- a/render/text.go +++ b/render/text.go @@ -30,7 +30,7 @@ func WriteString(w http.ResponseWriter, format string, data []interface{}) { writeContentType(w, plainContentType) if len(data) > 0 { fmt.Fprintf(w, format, data...) - } else { - io.WriteString(w, format) + return } + io.WriteString(w, format) } From c6110f970ca5af365283bd4083b8a105e974e92c Mon Sep 17 00:00:00 2001 From: Filip Figiel Date: Mon, 20 Aug 2018 09:15:31 +0200 Subject: [PATCH 206/912] Add PureJSON renderer (#694) Closes #693 --- README.md | 28 ++++++++++++++++++++++++++++ context_17.go | 17 +++++++++++++++++ context_17_test.go | 27 +++++++++++++++++++++++++++ context_test.go | 5 +++-- json/json.go | 1 + render/json_17.go | 28 ++++++++++++++++++++++++++++ render/render_17_test.go | 26 ++++++++++++++++++++++++++ render/render_test.go | 5 +++-- 8 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 context_17.go create mode 100644 context_17_test.go create mode 100644 render/json_17.go create mode 100644 render/render_17_test.go diff --git a/README.md b/README.md index ae183f9..053750b 100644 --- a/README.md +++ b/README.md @@ -991,6 +991,34 @@ func main() { } ``` +#### PureJSON + +Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. +This feature is unavailable in Go 1.6 and lower. + +```go +func main() { + r := gin.Default() + + // Serves unicode entities + r.GET("/json", func(c *gin.Context) { + c.JSON(200, gin.H{ + "html": "Hello, world!", + }) + }) + + // Serves literal characters + r.GET("/purejson", func(c *gin.Context) { + c.PureJSON(200, gin.H{ + "html": "Hello, world!", + }) + }) + + // listen and serve on 0.0.0.0:8080 + r.Run(":8080) +} +``` + ### Serving static files ```go diff --git a/context_17.go b/context_17.go new file mode 100644 index 0000000..8e9f75a --- /dev/null +++ b/context_17.go @@ -0,0 +1,17 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package gin + +import ( + "github.com/gin-gonic/gin/render" +) + +// PureJSON serializes the given struct as JSON into the response body. +// PureJSON, unlike JSON, does not replace special html characters with their unicode entities. +func (c *Context) PureJSON(code int, obj interface{}) { + c.Render(code, render.PureJSON{Data: obj}) +} diff --git a/context_17_test.go b/context_17_test.go new file mode 100644 index 0000000..d225190 --- /dev/null +++ b/context_17_test.go @@ -0,0 +1,27 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package gin + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// Tests that the response is serialized as JSON +// and Content-Type is set to application/json +// and special HTML characters are preserved +func TestContextRenderPureJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} diff --git a/context_test.go b/context_test.go index 675c2a4..782f7be 100644 --- a/context_test.go +++ b/context_test.go @@ -619,14 +619,15 @@ func TestContextRenderPanicIfErr(t *testing.T) { // Tests that the response is serialized as JSON // and Content-Type is set to application/json +// and special HTML characters are escaped func TestContextRenderJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.JSON(http.StatusCreated, H{"foo": "bar"}) + c.JSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } diff --git a/json/json.go b/json/json.go index aa76aa3..4f643c5 100644 --- a/json/json.go +++ b/json/json.go @@ -12,4 +12,5 @@ var ( Marshal = json.Marshal MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder + NewEncoder = json.NewEncoder ) diff --git a/render/json_17.go b/render/json_17.go new file mode 100644 index 0000000..df0dc14 --- /dev/null +++ b/render/json_17.go @@ -0,0 +1,28 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package render + +import ( + "net/http" + + "github.com/gin-gonic/gin/json" +) + +type PureJSON struct { + Data interface{} +} + +func (r PureJSON) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + encoder := json.NewEncoder(w) + encoder.SetEscapeHTML(false) + return encoder.Encode(r.Data) +} + +func (r PureJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonContentType) +} diff --git a/render/render_17_test.go b/render/render_17_test.go new file mode 100644 index 0000000..6833009 --- /dev/null +++ b/render/render_17_test.go @@ -0,0 +1,26 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package render + +import ( + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRenderPureJSON(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + "html": "", + } + err := (PureJSON{data}).Render(w) + assert.NoError(t, err) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) +} diff --git a/render/render_test.go b/render/render_test.go index cd20d01..fe9228e 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -52,7 +52,8 @@ func TestRenderMsgPack(t *testing.T) { func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ - "foo": "bar", + "foo": "bar", + "html": "", } (JSON{data}).WriteContentType(w) @@ -61,7 +62,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } From 0ebd42d0a921e49ed6a36fae33f2a2dcae800d6f Mon Sep 17 00:00:00 2001 From: junfengye Date: Mon, 20 Aug 2018 18:25:45 +0800 Subject: [PATCH 207/912] Update jsoniter.go (#1500) add newencoder to fix compile error for -tags=jsoniter --- json/jsoniter.go | 1 + 1 file changed, 1 insertion(+) diff --git a/json/jsoniter.go b/json/jsoniter.go index ffe1424..f1ed5be 100644 --- a/json/jsoniter.go +++ b/json/jsoniter.go @@ -13,4 +13,5 @@ var ( Marshal = json.Marshal MarshalIndent = json.MarshalIndent NewDecoder = json.NewDecoder + NewEncoder = json.NewEncoder ) From 85f3e78abcd408e7e155f58f72094446c4411e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 20 Aug 2018 21:49:24 +0800 Subject: [PATCH 208/912] chore: remove else instead of return/continue (#1502) As[ Effective Go](https://golang.org/doc/effective_go.html?#if) about `if` said, remove else statement instead of return/continue statement. --- binding/form_mapping.go | 16 ++++++++-------- context.go | 8 ++++---- render/json.go | 6 ++---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index a714291..f46a0dc 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -74,16 +74,16 @@ func mapForm(ptr interface{}, form map[string][]string) error { } } val.Field(i).Set(slice) - } else { - if _, isTime := structField.Interface().(time.Time); isTime { - if err := setTimeField(inputValue[0], typeField, structField); err != nil { - return err - } - continue - } - if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { + continue + } + if _, isTime := structField.Interface().(time.Time); isTime { + if err := setTimeField(inputValue[0], typeField, structField); err != nil { return err } + continue + } + if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { + return err } } return nil diff --git a/context.go b/context.go index 6d80284..063c72f 100644 --- a/context.go +++ b/context.go @@ -665,9 +665,9 @@ func (c *Context) Status(code int) { func (c *Context) Header(key, value string) { if value == "" { c.Writer.Header().Del(key) - } else { - c.Writer.Header().Set(key, value) + return } + c.Writer.Header().Set(key, value) } // GetHeader returns value from request headers. @@ -755,9 +755,9 @@ func (c *Context) JSONP(code int, obj interface{}) { callback := c.DefaultQuery("callback", "") if callback == "" { c.Render(code, render.JSON{Data: obj}) - } else { - c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) + return } + c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) } // JSON serializes the given struct as JSON into the response body. diff --git a/render/json.go b/render/json.go index 6e5089a..4d1857f 100644 --- a/render/json.go +++ b/render/json.go @@ -128,10 +128,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { var buffer bytes.Buffer for _, r := range string(ret) { - cvt := "" - if r < 128 { - cvt = string(r) - } else { + cvt := string(r) + if r >= 128 { cvt = fmt.Sprintf("\\u%04x", int64(r)) } buffer.WriteString(cvt) From 0da5b0c85ac2539e775a7e63aebf3e0b5808dab4 Mon Sep 17 00:00:00 2001 From: anoty Date: Tue, 21 Aug 2018 13:29:25 +0800 Subject: [PATCH 209/912] format readme code import (#1503) --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 053750b..9f66dd7 100644 --- a/README.md +++ b/README.md @@ -750,9 +750,12 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/742#issueco ```go package main -import "log" -import "github.com/gin-gonic/gin" -import "time" +import ( + "log" + "time" + + "github.com/gin-gonic/gin" +) type Person struct { Name string `form:"name"` From 09d342abbc35e473bfbe11abaecdfe237df0630d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 30 Aug 2018 14:22:51 +0800 Subject: [PATCH 210/912] Add golang 1.11.x testing (#1514) * Add golang 1.11.x testing * remove the latest golang testing See the issue: https://github.com/gin-gonic/gin/pull/1510 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e910156..a93458f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ go: - 1.8.x - 1.9.x - 1.10.x - - master + - 1.11.x git: depth: 10 From 708b76adf011840eb587d57804edf93b2ef4ba75 Mon Sep 17 00:00:00 2001 From: llgoer Date: Thu, 30 Aug 2018 14:29:26 +0800 Subject: [PATCH 211/912] Update README.md (#1509) change `ShouldBindXML` to `ShouldBindJSON` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f66dd7..7d13cf2 100644 --- a/README.md +++ b/README.md @@ -557,7 +557,7 @@ func main() { // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login - if err := c.ShouldBindXML(&json); err != nil { + if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } From 72db8acd99feab85e81ac6e2ebd16c76e0f986be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 30 Aug 2018 19:04:03 +0800 Subject: [PATCH 212/912] add internal package which includes json package (#1504) --- binding/json.go | 2 +- errors.go | 2 +- errors_test.go | 2 +- {json => internal/json}/json.go | 0 {json => internal/json}/jsoniter.go | 0 render/json.go | 2 +- render/json_17.go | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) rename {json => internal/json}/json.go (100%) rename {json => internal/json}/jsoniter.go (100%) diff --git a/binding/json.go b/binding/json.go index fea17bb..310922c 100644 --- a/binding/json.go +++ b/binding/json.go @@ -9,7 +9,7 @@ import ( "io" "net/http" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) // EnableDecoderUseNumber is used to call the UseNumber method on the JSON diff --git a/errors.go b/errors.go index dbfccd8..6f90377 100644 --- a/errors.go +++ b/errors.go @@ -9,7 +9,7 @@ import ( "fmt" "reflect" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) type ErrorType uint64 diff --git a/errors_test.go b/errors_test.go index 0626611..9351b57 100644 --- a/errors_test.go +++ b/errors_test.go @@ -8,7 +8,7 @@ import ( "errors" "testing" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" "github.com/stretchr/testify/assert" ) diff --git a/json/json.go b/internal/json/json.go similarity index 100% rename from json/json.go rename to internal/json/json.go diff --git a/json/jsoniter.go b/internal/json/jsoniter.go similarity index 100% rename from json/jsoniter.go rename to internal/json/jsoniter.go diff --git a/render/json.go b/render/json.go index 4d1857f..f6b7878 100644 --- a/render/json.go +++ b/render/json.go @@ -10,7 +10,7 @@ import ( "html/template" "net/http" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) type JSON struct { diff --git a/render/json_17.go b/render/json_17.go index df0dc14..75f7f37 100644 --- a/render/json_17.go +++ b/render/json_17.go @@ -9,7 +9,7 @@ package render import ( "net/http" - "github.com/gin-gonic/gin/json" + "github.com/gin-gonic/gin/internal/json" ) type PureJSON struct { From 7451a402bbab9dd439b09aa377f18638c7d8fd55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 30 Aug 2018 23:36:53 +0800 Subject: [PATCH 213/912] chore: update vendor version (#1520) #1491 adds some lib when upgrade json-iterator but it is not needed, and use `v1.1.5` not `1.1.5` version for json-iterator. --- vendor/vendor.json | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index e6d038a..86df11b 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -27,8 +27,8 @@ "path": "github.com/json-iterator/go", "revision": "1624edc4454b8682399def8740d46db5e4362ba4", "revisionTime": "2018-08-06T06:07:27Z", - "version": "1.1.5", - "versionExact": "1.1.5" + "version": "v1.1", + "versionExact": "v1.1.5" }, { "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=", @@ -38,24 +38,6 @@ "version": "v0.0.3", "versionExact": "v0.0.3" }, - { - "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", - "path": "github.com/modern-go/concurrent", - "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", - "revisionTime": "2018-03-06T01:26:44Z" - }, - { - "checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=", - "path": "github.com/modern-go/reflect2", - "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", - "revisionTime": "2018-07-18T01:23:57Z" - }, - { - "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", - "path": "github.com/pmezard/go-difflib/difflib", - "revision": "792786c7400a136282c1664665ae0a8db921c6c2", - "revisionTime": "2016-01-10T10:55:54Z" - }, { "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", @@ -79,12 +61,6 @@ "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revisionTime": "2016-10-18T08:54:36Z" }, - { - "checksumSHA1": "7Gocawl8bm27cpAILtuf21xvVD8=", - "path": "golang.org/x/sys/unix", - "revision": "1c9583448a9c3aa0f9a6a5241bf73c0bd8aafded", - "revisionTime": "2018-08-15T07:37:39Z" - }, { "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", "path": "gopkg.in/go-playground/validator.v8", From 705e1992981b974ce131303cdf415ff78114f728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 1 Sep 2018 00:40:33 +0800 Subject: [PATCH 214/912] chore: update issue_implate (#1524) --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index de067ce..9d49aa4 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,8 +3,8 @@ - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. +- go version: - gin version (or commit ref): -- git version: - operating system: ## Description From 500ebd9ea866c57427aeaddc31f374adedd4084b Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Fri, 31 Aug 2018 22:38:16 +0200 Subject: [PATCH 215/912] docs: add fnproject to gin's user list (#1505) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d13cf2..a4bc4e7 100644 --- a/README.md +++ b/README.md @@ -1885,5 +1885,6 @@ func TestPingRoute(t *testing.T) { Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. -* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go +* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go. * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. +* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. From df1e17c2f0ca7179b5acf03ce1da5c4f07c4dd7d Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Wed, 12 Sep 2018 02:13:16 +0100 Subject: [PATCH 216/912] remove debug print statements from test code (#1540) Found using https://go-critic.github.io/overview#commentedOutCode-ref --- tree_test.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tree_test.go b/tree_test.go index 5bc2717..152f633 100644 --- a/tree_test.go +++ b/tree_test.go @@ -125,8 +125,6 @@ func TestTreeAddAndGet(t *testing.T) { tree.addRoute(route, fakeHandler(route)) } - //printChildren(tree, "") - checkRequests(t, tree, testRequests{ {"/a", false, "/a", nil}, {"/", true, "", nil}, @@ -168,8 +166,6 @@ func TestTreeWildcard(t *testing.T) { tree.addRoute(route, fakeHandler(route)) } - //printChildren(tree, "") - checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, @@ -208,7 +204,6 @@ func TestUnescapeParameters(t *testing.T) { tree.addRoute(route, fakeHandler(route)) } - //printChildren(tree, "") unescape := true checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, @@ -260,8 +255,6 @@ func testRoutes(t *testing.T, routes []testRoute) { t.Errorf("unexpected panic for route '%s': %v", route.path, recv) } } - - //printChildren(tree, "") } func TestTreeWildcardConflict(t *testing.T) { @@ -328,8 +321,6 @@ func TestTreeDupliatePath(t *testing.T) { } } - //printChildren(tree, "") - checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/doc/", false, "/doc/", nil}, @@ -444,8 +435,6 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { } } - //printChildren(tree, "") - tsrRoutes := [...]string{ "/hi/", "/b", From 3f27866f806536be319b3e342001dd4e0fb81691 Mon Sep 17 00:00:00 2001 From: "Iskander (Alex) Sharipov" Date: Wed, 12 Sep 2018 14:21:26 +0100 Subject: [PATCH 217/912] simplify slice expressions: s[:] => s (#1541) Found using https://go-critic.github.io/overview#unslice-ref --- context_test.go | 2 +- gin_test.go | 20 ++++++++++---------- render/render_test.go | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/context_test.go b/context_test.go index 782f7be..4c30708 100644 --- a/context_test.go +++ b/context_test.go @@ -978,7 +978,7 @@ func TestContextRenderProtoBuf(t *testing.T) { assert.NoError(t, err) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type")) } diff --git a/gin_test.go b/gin_test.go index b3cab71..95b9cc1 100644 --- a/gin_test.go +++ b/gin_test.go @@ -88,7 +88,7 @@ func TestLoadHTMLGlob(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -101,7 +101,7 @@ func TestLoadHTMLGlob2(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -114,7 +114,7 @@ func TestLoadHTMLGlob3(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -134,7 +134,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -148,7 +148,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + assert.Equal(t, "Date: 2017/07/01\n", string(resp)) td() } @@ -187,7 +187,7 @@ func TestLoadHTMLFiles(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -199,7 +199,7 @@ func TestLoadHTMLFiles2(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -211,7 +211,7 @@ func TestLoadHTMLFiles3(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -230,7 +230,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "

Hello world

", string(resp[:])) + assert.Equal(t, "

Hello world

", string(resp)) td() } @@ -243,7 +243,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "Date: 2017/07/01\n", string(resp[:])) + assert.Equal(t, "Date: 2017/07/01\n", string(resp)) td() } diff --git a/render/render_test.go b/render/render_test.go index fe9228e..09ccc65 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -287,7 +287,7 @@ func TestRenderProtoBuf(t *testing.T) { err = (ProtoBuf{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, string(protoData[:]), w.Body.String()) + assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } From d510595aa58c2417373d89a8d8ffa21cf58673cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 15 Sep 2018 10:23:32 +0800 Subject: [PATCH 218/912] chore: add some annotations (#1544) ref: #1075 because I am not a native English, maybe have a bit problem. --- README.md | 1 + examples/basic/main.go | 6 +++--- examples/custom-validation/server.go | 1 + examples/realtime-advanced/main.go | 3 +++ examples/realtime-advanced/stats.go | 1 + ginS/gins.go | 9 ++++++++- internal/json/json.go | 10 +++++++--- internal/json/jsoniter.go | 12 ++++++++---- render/data.go | 2 ++ render/html.go | 14 +++++++++++++- render/json.go | 17 +++++++++++++++++ render/json_17.go | 3 +++ render/msgpack.go | 4 ++++ render/protobuf.go | 3 +++ render/reader.go | 3 +++ render/redirect.go | 3 +++ render/render.go | 3 +++ render/text.go | 4 ++++ render/xml.go | 3 +++ render/yaml.go | 3 +++ utils.go | 8 ++++---- 21 files changed, 97 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a4bc4e7..9a97ec5 100644 --- a/README.md +++ b/README.md @@ -657,6 +657,7 @@ import ( "gopkg.in/go-playground/validator.v8" ) +// Booking contains binded and validated data. 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"` diff --git a/examples/basic/main.go b/examples/basic/main.go index 48fa7bb..1c9e0ac 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" ) -var DB = make(map[string]string) +var db = make(map[string]string) func setupRouter() *gin.Engine { // Disable Console Color @@ -21,7 +21,7 @@ func setupRouter() *gin.Engine { // Get user value r.GET("/user/:name", func(c *gin.Context) { user := c.Params.ByName("name") - value, ok := DB[user] + value, ok := db[user] if ok { c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) } else { @@ -50,7 +50,7 @@ func setupRouter() *gin.Engine { } if c.Bind(&json) == nil { - DB[user] = json.Value + db[user] = json.Value c.JSON(http.StatusOK, gin.H{"status": "ok"}) } }) diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go index dea0c30..9238200 100644 --- a/examples/custom-validation/server.go +++ b/examples/custom-validation/server.go @@ -10,6 +10,7 @@ import ( "gopkg.in/go-playground/validator.v8" ) +// Booking contains binded and validated data. 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"` diff --git a/examples/realtime-advanced/main.go b/examples/realtime-advanced/main.go index 1f3c858..a16c58b 100644 --- a/examples/realtime-advanced/main.go +++ b/examples/realtime-advanced/main.go @@ -13,16 +13,19 @@ func main() { StartGin() } +// ConfigRuntime sets the number of operating system threads. func ConfigRuntime() { nuCPU := runtime.NumCPU() runtime.GOMAXPROCS(nuCPU) fmt.Printf("Running with %d CPUs\n", nuCPU) } +// StartWrokers start starsWorker by goroutine. func StartWorkers() { go statsWorker() } +// StartGin starts gin web server with setting router. func StartGin() { gin.SetMode(gin.ReleaseMode) diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go index 4afedcb..a648803 100644 --- a/examples/realtime-advanced/stats.go +++ b/examples/realtime-advanced/stats.go @@ -50,6 +50,7 @@ func connectedUsers() uint64 { return uint64(connected) } +// Stats returns savedStats data. func Stats() map[string]uint64 { mutexStats.RLock() defer mutexStats.RUnlock() diff --git a/ginS/gins.go b/ginS/gins.go index a7686f2..04cf131 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -22,14 +22,17 @@ func engine() *gin.Engine { return internalEngine } +// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob. func LoadHTMLGlob(pattern string) { engine().LoadHTMLGlob(pattern) } +// LoadHTMLFiles is a wrapper for Engine.LoadHTMLFiles. func LoadHTMLFiles(files ...string) { engine().LoadHTMLFiles(files...) } +// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate. func SetHTMLTemplate(templ *template.Template) { engine().SetHTMLTemplate(templ) } @@ -39,7 +42,7 @@ func NoRoute(handlers ...gin.HandlerFunc) { engine().NoRoute(handlers...) } -// NoMethod sets the handlers called when... TODO +// NoMethod is a wrapper for Engine.NoMethod. func NoMethod(handlers ...gin.HandlerFunc) { engine().NoMethod(handlers...) } @@ -50,6 +53,7 @@ func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup { return engine().Group(relativePath, handlers...) } +// Handle is a wrapper for Engine.Handle. func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Handle(httpMethod, relativePath, handlers...) } @@ -89,10 +93,12 @@ func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().HEAD(relativePath, handlers...) } +// Any is a wrapper for Engine.Any. func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Any(relativePath, handlers...) } +// StaticFile is a wrapper for Engine.StaticFile. func StaticFile(relativePath, filepath string) gin.IRoutes { return engine().StaticFile(relativePath, filepath) } @@ -107,6 +113,7 @@ func Static(relativePath, root string) gin.IRoutes { return engine().Static(relativePath, root) } +// StaticFS is a wrapper for Engine.StaticFS. func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes { return engine().StaticFS(relativePath, fs) } diff --git a/internal/json/json.go b/internal/json/json.go index 4f643c5..419d35f 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -9,8 +9,12 @@ package json import "encoding/json" var ( - Marshal = json.Marshal + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent - NewDecoder = json.NewDecoder - NewEncoder = json.NewEncoder + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder ) diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index f1ed5be..2021c53 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -9,9 +9,13 @@ package json import "github.com/json-iterator/go" var ( - json = jsoniter.ConfigCompatibleWithStandardLibrary - Marshal = json.Marshal + json = jsoniter.ConfigCompatibleWithStandardLibrary + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent - NewDecoder = json.NewDecoder - NewEncoder = json.NewEncoder + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder ) diff --git a/render/data.go b/render/data.go index 3319491..6ba657b 100644 --- a/render/data.go +++ b/render/data.go @@ -6,6 +6,7 @@ package render import "net/http" +// Data contains ContentType and bytes data. type Data struct { ContentType string Data []byte @@ -18,6 +19,7 @@ func (r Data) Render(w http.ResponseWriter) (err error) { return } +// WriteContentType (Data) writes custom ContentType. func (r Data) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } diff --git a/render/html.go b/render/html.go index 1e3be65..4d3d581 100644 --- a/render/html.go +++ b/render/html.go @@ -9,20 +9,27 @@ import ( "net/http" ) +// Delims represents a set of Left and Right delimiters for HTML template rendering. type Delims struct { - Left string + // Left delimiter, defaults to {{. + Left string + // Right delimiter, defaults to }}. Right string } +// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug. type HTMLRender interface { + // Instance returns an HTML instance. Instance(string, interface{}) Render } +// HTMLProduction contains template reference and its delims. type HTMLProduction struct { Template *template.Template Delims Delims } +// HTMLDebug contains template delims and pattern and function with file list. type HTMLDebug struct { Files []string Glob string @@ -30,6 +37,7 @@ type HTMLDebug struct { FuncMap template.FuncMap } +// HTML contains template reference and its name with given interface object. type HTML struct { Template *template.Template Name string @@ -38,6 +46,7 @@ type HTML struct { var htmlContentType = []string{"text/html; charset=utf-8"} +// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.. func (r HTMLProduction) Instance(name string, data interface{}) Render { return HTML{ Template: r.Template, @@ -46,6 +55,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render { } } +// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.. func (r HTMLDebug) Instance(name string, data interface{}) Render { return HTML{ Template: r.loadTemplate(), @@ -66,6 +76,7 @@ func (r HTMLDebug) loadTemplate() *template.Template { panic("the HTML debug render was created without files or glob pattern") } +// Render (HTML) executes template and writes its result with custom ContentType for response. func (r HTML) Render(w http.ResponseWriter) error { r.WriteContentType(w) @@ -75,6 +86,7 @@ func (r HTML) Render(w http.ResponseWriter) error { return r.Template.ExecuteTemplate(w, r.Name, r.Data) } +// WriteContentType (HTML) writes HTML ContentType. func (r HTML) WriteContentType(w http.ResponseWriter) { writeContentType(w, htmlContentType) } diff --git a/render/json.go b/render/json.go index f6b7878..32d0fc4 100644 --- a/render/json.go +++ b/render/json.go @@ -13,34 +13,41 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +// JSON contains the given interface object. type JSON struct { Data interface{} } +// IndentedJSON contains the given interface object. type IndentedJSON struct { Data interface{} } +// SecureJSON contains the given interface object and its prefix. type SecureJSON struct { Prefix string Data interface{} } +// JsonpJSON contains the given interface object its callback. type JsonpJSON struct { Callback string Data interface{} } +// AsciiJSON contains the given interface object. type AsciiJSON struct { Data interface{} } +// SecureJSONPrefix is a string which represents SecureJSON prefix. type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} var jsonAsciiContentType = []string{"application/json"} +// Render (JSON) writes data with custom ContentType. func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { panic(err) @@ -48,10 +55,12 @@ func (r JSON) Render(w http.ResponseWriter) (err error) { return } +// WriteContentType (JSON) writes JSON ContentType. func (r JSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) jsonBytes, err := json.Marshal(obj) @@ -62,6 +71,7 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { return nil } +// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. func (r IndentedJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, err := json.MarshalIndent(r.Data, "", " ") @@ -72,10 +82,12 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (IndentedJSON) writes JSON ContentType. func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType. func (r SecureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, err := json.Marshal(r.Data) @@ -90,10 +102,12 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (SecureJSON) writes JSON ContentType. func (r SecureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } +// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType. func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) ret, err := json.Marshal(r.Data) @@ -115,10 +129,12 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { return nil } +// WriteContentType (JsonpJSON) writes Javascript ContentType. func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonpContentType) } +// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType. func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) ret, err := json.Marshal(r.Data) @@ -139,6 +155,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { return nil } +// WriteContentType (AsciiJSON) writes JSON ContentType. func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonAsciiContentType) } diff --git a/render/json_17.go b/render/json_17.go index 75f7f37..208193c 100644 --- a/render/json_17.go +++ b/render/json_17.go @@ -12,10 +12,12 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +// PureJSON contains the given interface object. type PureJSON struct { Data interface{} } +// Render (PureJSON) writes custom ContentType and encodes the given interface object. func (r PureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) encoder := json.NewEncoder(w) @@ -23,6 +25,7 @@ func (r PureJSON) Render(w http.ResponseWriter) error { return encoder.Encode(r.Data) } +// WriteContentType (PureJSON) writes custom ContentType. func (r PureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } diff --git a/render/msgpack.go b/render/msgpack.go index b6b8aa0..dc681fc 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -10,20 +10,24 @@ import ( "github.com/ugorji/go/codec" ) +// MsgPack contains the given interface object. type MsgPack struct { Data interface{} } var msgpackContentType = []string{"application/msgpack; charset=utf-8"} +// WriteContentType (MsgPack) writes MsgPack ContentType. func (r MsgPack) WriteContentType(w http.ResponseWriter) { writeContentType(w, msgpackContentType) } +// Render (MsgPack) encodes the given interface object and writes data with custom ContentType. func (r MsgPack) Render(w http.ResponseWriter) error { return WriteMsgPack(w, r.Data) } +// WriteMsgPack writes MsgPack ContentType and encodes the given interface object. func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { writeContentType(w, msgpackContentType) var mh codec.MsgpackHandle diff --git a/render/protobuf.go b/render/protobuf.go index 34f1e9b..4789507 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -10,12 +10,14 @@ import ( "github.com/golang/protobuf/proto" ) +// ProtoBuf contains the given interface object. type ProtoBuf struct { Data interface{} } var protobufContentType = []string{"application/x-protobuf"} +// Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType. func (r ProtoBuf) Render(w http.ResponseWriter) error { r.WriteContentType(w) @@ -28,6 +30,7 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (ProtoBuf) writes ProtoBuf ContentType. func (r ProtoBuf) WriteContentType(w http.ResponseWriter) { writeContentType(w, protobufContentType) } diff --git a/render/reader.go b/render/reader.go index 7a06cce..ab60e53 100644 --- a/render/reader.go +++ b/render/reader.go @@ -10,6 +10,7 @@ import ( "strconv" ) +// Reader contains the IO reader and its length, and custom ContentType and other headers. type Reader struct { ContentType string ContentLength int64 @@ -26,10 +27,12 @@ func (r Reader) Render(w http.ResponseWriter) (err error) { return } +// WriteContentType (Reader) writes custom ContentType. func (r Reader) WriteContentType(w http.ResponseWriter) { writeContentType(w, []string{r.ContentType}) } +// writeHeaders writes custom Header. func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { header := w.Header() for k, v := range headers { diff --git a/render/redirect.go b/render/redirect.go index a0634f5..9c145fe 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -9,12 +9,14 @@ import ( "net/http" ) +// Redirect contains the http request reference and redirects status code and location. type Redirect struct { Code int Request *http.Request Location string } +// Render (Redirect) redirects the http request to new location and writes redirect response. func (r Redirect) Render(w http.ResponseWriter) error { // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) // when we upgrade go version we can use http.StatusPermanentRedirect @@ -25,4 +27,5 @@ func (r Redirect) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (Redirect) don't write any ContentType. func (r Redirect) WriteContentType(http.ResponseWriter) {} diff --git a/render/render.go b/render/render.go index df0d1d7..abfc79f 100644 --- a/render/render.go +++ b/render/render.go @@ -6,8 +6,11 @@ package render import "net/http" +// Render interface is to be implemented by JSON, XML, HTML, YAML and so on. type Render interface { + // Render writes data with custom ContentType. Render(http.ResponseWriter) error + // WriteContentType writes custom ContentType. WriteContentType(w http.ResponseWriter) } diff --git a/render/text.go b/render/text.go index 7a6acc4..2ea7343 100644 --- a/render/text.go +++ b/render/text.go @@ -10,6 +10,7 @@ import ( "net/http" ) +// String contains the given interface object slice and its format. type String struct { Format string Data []interface{} @@ -17,15 +18,18 @@ type String struct { var plainContentType = []string{"text/plain; charset=utf-8"} +// Render (String) writes data with custom ContentType. func (r String) Render(w http.ResponseWriter) error { WriteString(w, r.Format, r.Data) return nil } +// WriteContentType (String) writes Plain ContentType. func (r String) WriteContentType(w http.ResponseWriter) { writeContentType(w, plainContentType) } +// WriteString writes data according to its format and write custom ContentType. func WriteString(w http.ResponseWriter, format string, data []interface{}) { writeContentType(w, plainContentType) if len(data) > 0 { diff --git a/render/xml.go b/render/xml.go index cff1ac3..cc5390a 100644 --- a/render/xml.go +++ b/render/xml.go @@ -9,17 +9,20 @@ import ( "net/http" ) +// XML contains the given interface object. type XML struct { Data interface{} } var xmlContentType = []string{"application/xml; charset=utf-8"} +// Render (XML) encodes the given interface object and writes data with custom ContentType. func (r XML) Render(w http.ResponseWriter) error { r.WriteContentType(w) return xml.NewEncoder(w).Encode(r.Data) } +// WriteContentType (XML) writes XML ContentType for response. func (r XML) WriteContentType(w http.ResponseWriter) { writeContentType(w, xmlContentType) } diff --git a/render/yaml.go b/render/yaml.go index 25d0ebd..33bc325 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -10,12 +10,14 @@ import ( "gopkg.in/yaml.v2" ) +// YAML contains the given interface object. type YAML struct { Data interface{} } var yamlContentType = []string{"application/x-yaml; charset=utf-8"} +// Render (YAML) marshals the given interface object and writes data with custom ContentType. func (r YAML) Render(w http.ResponseWriter) error { r.WriteContentType(w) @@ -28,6 +30,7 @@ func (r YAML) Render(w http.ResponseWriter) error { return nil } +// WriteContentType (YAML) writes YAML ContentType for response. func (r YAML) WriteContentType(w http.ResponseWriter) { writeContentType(w, yamlContentType) } diff --git a/utils.go b/utils.go index bf32c77..f4532d5 100644 --- a/utils.go +++ b/utils.go @@ -14,8 +14,10 @@ import ( "strings" ) +// BindKey indicates a default bind key. const BindKey = "_gin-gonic/gin/bindkey" +// Bind is a helper function for given interface object and returns a Gin middleware. func Bind(val interface{}) HandlerFunc { value := reflect.ValueOf(val) if value.Kind() == reflect.Ptr { @@ -33,16 +35,14 @@ func Bind(val interface{}) HandlerFunc { } } -// WrapF is a helper function for wrapping http.HandlerFunc -// Returns a Gin middleware +// WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware. func WrapF(f http.HandlerFunc) HandlerFunc { return func(c *Context) { f(c.Writer, c.Request) } } -// WrapH is a helper function for wrapping http.Handler -// Returns a Gin middleware +// WrapH is a helper function for wrapping http.Handler and returns a Gin middleware. func WrapH(h http.Handler) HandlerFunc { return func(c *Context) { h.ServeHTTP(c.Writer, c.Request) From 6db092f778277ef13da427acfa3501890e952436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 15 Sep 2018 15:21:54 +0800 Subject: [PATCH 219/912] chore: add some annotations (#1550) ref #1075 should all annotations and can close #1075 . --- context.go | 6 ++++++ errors.go | 20 +++++++++++++++----- examples/realtime-advanced/main.go | 2 +- gin.go | 6 ++++++ mode.go | 13 +++++++++++-- render/html.go | 4 ++-- routergroup.go | 8 ++++++-- 7 files changed, 47 insertions(+), 12 deletions(-) diff --git a/context.go b/context.go index 063c72f..809902a 100644 --- a/context.go +++ b/context.go @@ -711,6 +711,7 @@ func (c *Context) Cookie(name string) (string, error) { return val, nil } +// Render writes the response headers and calls render.Render to render data. func (c *Context) Render(code int, r render.Render) { c.Status(code) @@ -833,6 +834,7 @@ func (c *Context) SSEvent(name string, message interface{}) { }) } +// Stream sends a streaming response. func (c *Context) Stream(step func(w io.Writer) bool) { w := c.Writer clientGone := w.CloseNotify() @@ -854,6 +856,7 @@ func (c *Context) Stream(step func(w io.Writer) bool) { /******** CONTENT NEGOTIATION *******/ /************************************/ +// Negotiate contains all negotiations data. type Negotiate struct { Offered []string HTMLName string @@ -863,6 +866,7 @@ type Negotiate struct { Data interface{} } +// Negotiate calls different Render according acceptable Accept format. func (c *Context) Negotiate(code int, config Negotiate) { switch c.NegotiateFormat(config.Offered...) { case binding.MIMEJSON: @@ -882,6 +886,7 @@ func (c *Context) Negotiate(code int, config Negotiate) { } } +// NegotiateFormat returns an acceptable Accept format. func (c *Context) NegotiateFormat(offered ...string) string { assert1(len(offered) > 0, "you must provide at least one offer") @@ -901,6 +906,7 @@ func (c *Context) NegotiateFormat(offered ...string) string { return "" } +// SetAccepted sets Accept header data. func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } diff --git a/errors.go b/errors.go index 6f90377..477b9d5 100644 --- a/errors.go +++ b/errors.go @@ -12,18 +12,24 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +// ErrorType is an unsigned 64-bit error code as defined in the gin spec. type ErrorType uint64 const ( - ErrorTypeBind ErrorType = 1 << 63 // used when c.Bind() fails - ErrorTypeRender ErrorType = 1 << 62 // used when c.Render() fails + // ErrorTypeBind is used when Context.Bind() fails. + ErrorTypeBind ErrorType = 1 << 63 + // ErrorTypeRender is used when Context.Render() fails. + ErrorTypeRender ErrorType = 1 << 62 + // ErrorTypePrivate indicates a private error. ErrorTypePrivate ErrorType = 1 << 0 - ErrorTypePublic ErrorType = 1 << 1 - + // ErrorTypePublic indicates a public error. + ErrorTypePublic ErrorType = 1 << 1 + // ErrorTypeAny indicates other any error. ErrorTypeAny ErrorType = 1<<64 - 1 ErrorTypeNu = 2 ) +// Error represents a error's specification. type Error struct { Err error Type ErrorType @@ -34,11 +40,13 @@ type errorMsgs []*Error var _ error = &Error{} +// SetType sets the error's type. func (msg *Error) SetType(flags ErrorType) *Error { msg.Type = flags return msg } +// SetMeta sets the error's meta data. func (msg *Error) SetMeta(data interface{}) *Error { msg.Meta = data return msg @@ -70,11 +78,12 @@ func (msg *Error) MarshalJSON() ([]byte, error) { return json.Marshal(msg.JSON()) } -// Error implements the error interface +// Error implements the error interface. func (msg Error) Error() string { return msg.Err.Error() } +// IsType judges one error. func (msg *Error) IsType(flags ErrorType) bool { return (msg.Type & flags) > 0 } @@ -138,6 +147,7 @@ func (a errorMsgs) JSON() interface{} { } } +// MarshalJSON implements the json.Marshaller interface. func (a errorMsgs) MarshalJSON() ([]byte, error) { return json.Marshal(a.JSON()) } diff --git a/examples/realtime-advanced/main.go b/examples/realtime-advanced/main.go index a16c58b..f3ead47 100644 --- a/examples/realtime-advanced/main.go +++ b/examples/realtime-advanced/main.go @@ -20,7 +20,7 @@ func ConfigRuntime() { fmt.Printf("Running with %d CPUs\n", nuCPU) } -// StartWrokers start starsWorker by goroutine. +// StartWorkers start starsWorker by goroutine. func StartWorkers() { go statsWorker() } diff --git a/gin.go b/gin.go index aa62e01..6bff3bd 100644 --- a/gin.go +++ b/gin.go @@ -26,7 +26,10 @@ var ( defaultAppEngine bool ) +// HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) + +// HandlersChain defines a HandlerFunc array. type HandlersChain []HandlerFunc // Last returns the last handler in the chain. ie. the last handler is the main own. @@ -37,12 +40,14 @@ func (c HandlersChain) Last() HandlerFunc { return nil } +// RouteInfo represents a request route's specification which contains method and path and its handler. type RouteInfo struct { Method string Path string Handler string } +// RoutesInfo defines a RouteInfo array. type RoutesInfo []RouteInfo // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. @@ -155,6 +160,7 @@ func (engine *Engine) allocateContext() *Context { return &Context{engine: engine} } +// Delims sets template left and right delims and returns a Engine instance. func (engine *Engine) Delims(left, right string) *Engine { engine.delims = render.Delims{Left: left, Right: right} return engine diff --git a/mode.go b/mode.go index 9df4e45..7cb0143 100644 --- a/mode.go +++ b/mode.go @@ -11,12 +11,16 @@ import ( "github.com/gin-gonic/gin/binding" ) +// ENV_GIN_MODE indicates environment name for gin mode. const ENV_GIN_MODE = "GIN_MODE" const ( - DebugMode = "debug" + // DebugMode indicates gin mode is debug. + DebugMode = "debug" + // ReleaseMode indicates gin mode is relase. ReleaseMode = "release" - TestMode = "test" + // TestMode indicates gin mode is test. + TestMode = "test" ) const ( debugCode = iota @@ -42,6 +46,7 @@ func init() { SetMode(mode) } +// SetMode sets gin mode according to input string. func SetMode(value string) { switch value { case DebugMode, "": @@ -59,14 +64,18 @@ func SetMode(value string) { modeName = value } +// DisableBindValidation closes the default validator. func DisableBindValidation() { binding.Validator = nil } +// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to +// call the UseNumber method on the JSON Decoder instance. func EnableJsonDecoderUseNumber() { binding.EnableDecoderUseNumber = true } +// Mode returns currently gin mode. func Mode() string { return modeName } diff --git a/render/html.go b/render/html.go index 4d3d581..6696ece 100644 --- a/render/html.go +++ b/render/html.go @@ -46,7 +46,7 @@ type HTML struct { var htmlContentType = []string{"text/html; charset=utf-8"} -// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.. +// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface. func (r HTMLProduction) Instance(name string, data interface{}) Render { return HTML{ Template: r.Template, @@ -55,7 +55,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render { } } -// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.. +// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface. func (r HTMLDebug) Instance(name string, data interface{}) Render { return HTML{ Template: r.loadTemplate(), diff --git a/routergroup.go b/routergroup.go index 876a61b..be561de 100644 --- a/routergroup.go +++ b/routergroup.go @@ -11,11 +11,13 @@ import ( "strings" ) +// IRouter defines all router handle interface includes single and group router. type IRouter interface { IRoutes Group(string, ...HandlerFunc) *RouterGroup } +// Iroutes defins all router handle interface. type IRoutes interface { Use(...HandlerFunc) IRoutes @@ -34,8 +36,8 @@ type IRoutes interface { StaticFS(string, http.FileSystem) IRoutes } -// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix -// and an array of handlers (middleware). +// RouterGroup is used internally to configure router, a RouterGroup is associated with +// a prefix and an array of handlers (middleware). type RouterGroup struct { Handlers HandlersChain basePath string @@ -61,6 +63,8 @@ func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *R } } +// BasePath returns the base path of router group. +// For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api". func (group *RouterGroup) BasePath() string { return group.basePath } From 7c7f703cc559af6c67fc82c3c39959a0c7d42b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 16 Sep 2018 23:22:54 +0800 Subject: [PATCH 220/912] initial go.mod module definition (#1554) --- go.mod | 27 ++++++++++++++++++++++++ go.sum | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bd4ad97 --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module github.com/gin-gonic/gin + +require ( + github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 // indirect + github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 + github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 + github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b + github.com/golang/protobuf v1.2.0 + github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 + github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b + github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 + github.com/mattn/go-isatty v0.0.3 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.2 + github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6 + github.com/ugorji/go v1.1.1 + golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b + golang.org/x/net v0.0.0-20180826012351-8a410e7b638d + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f + golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect + google.golang.org/grpc v1.15.0 + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/go-playground/validator.v8 v8.18.2 + gopkg.in/yaml.v2 v2.2.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3382bee --- /dev/null +++ b/go.sum @@ -0,0 +1,65 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 h1:HqkufMBR7waVfFFRABWqHa1WgTvjtVDJTLJe3CR576A= +github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 h1:QnnoVdChKs+GeTvN4rPYTW6b5U6M3HMEvQ/+x4IGtfY= +github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66/go.mod h1:kTEh6M2J/mh7nsskr28alwLCXm/DSG5OSA/o31yy2XU= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8Jc6zMvyRz3PCQrTTCXnVRvEzyBcM890= +github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 h1:cW/amwGEJK5MSKntPXRjX4dxs/nGxGT8gXKIsKFmHGc= +github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15/go.mod h1:Fdm/oWRW+CH8PRbLntksCNtmcCBximKPkVQYvmMl80k= +github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b h1:X61dhFTE1Au92SvyF8HyAwdjWqiSdfBgFR7wTxC0+uU= +github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= +github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6 h1:d/LEgOfWe+AlOCz/kzmkvlO+gq9LRGhjSHqt2nx8Muc= +github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= +github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= +github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw= +google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 90c680ef5c360ccd2f0f5f9f0ab1f44f3ce9eef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laforge?= Date: Mon, 17 Sep 2018 06:09:34 +0200 Subject: [PATCH 221/912] Let's user define how he wants to log his routes (eg. JSON, key value, or something else) (#1553) (#1555) --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ debug.go | 8 +++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a97ec5..45bfce5 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [http2 server push](#http2-server-push) + - [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Testing](#testing) - [Users](#users) @@ -1836,6 +1837,49 @@ func main() { } ``` +### Define format for the log of routes + +The default log of routes is: +``` +[GIN-debug] POST /foo --> main.main.func1 (3 handlers) +[GIN-debug] GET /bar --> main.main.func2 (3 handlers) +[GIN-debug] GET /status --> main.main.func3 (3 handlers) +``` + +If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. +In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. +```go +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) + + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) + + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + + // Listen and Server in http://0.0.0.0:8080 + r.Run() +} +``` + + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/debug.go b/debug.go index f11156b..693486a 100644 --- a/debug.go +++ b/debug.go @@ -20,11 +20,17 @@ func IsDebugging() bool { return ginMode == debugCode } +var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) + func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { if IsDebugging() { nuHandlers := len(handlers) handlerName := nameOfFunction(handlers.Last()) - debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) + if DebugPrintRouteFunc == nil { + debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) + } else { + DebugPrintRouteFunc(httpMethod, absolutePath, handlerName, nuHandlers) + } } } From b27b7026c70697d84226f15d6d433bdf07023c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 17 Sep 2018 15:08:11 +0800 Subject: [PATCH 222/912] chore: add a version file includes gin version (#1549) * chore: add a version file includes gin version * update version for dev version --- gin.go | 6 +----- version.go | 8 ++++++++ 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 version.go diff --git a/gin.go b/gin.go index 6bff3bd..11f6d94 100644 --- a/gin.go +++ b/gin.go @@ -14,11 +14,7 @@ import ( "github.com/gin-gonic/gin/render" ) -const ( - // Version is Framework's version. - Version = "v1.3.0" - defaultMultipartMemory = 32 << 20 // 32 MB -) +const defaultMultipartMemory = 32 << 20 // 32 MB var ( default404Body = []byte("404 page not found") diff --git a/version.go b/version.go new file mode 100644 index 0000000..028caeb --- /dev/null +++ b/version.go @@ -0,0 +1,8 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +// Version is the current gin framework's version. +const Version = "v1.4.0-dev" From 07f1bf0e63512fbf9c7d7984d235a399eb916244 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 19 Sep 2018 13:57:00 +0800 Subject: [PATCH 223/912] feat: replace debug log with fmt package. (#1560) --- context_test.go | 12 ++--- debug.go | 8 +-- debug_test.go | 140 +++++++++++++++++++++++++++--------------------- 3 files changed, 86 insertions(+), 74 deletions(-) diff --git a/context_test.go b/context_test.go index 4c30708..5a5bb6e 100644 --- a/context_test.go +++ b/context_test.go @@ -784,14 +784,14 @@ func TestContextRenderHTML2(t *testing.T) { router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 1) - var b bytes.Buffer - setup(&b) - defer teardown() - templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) - router.SetHTMLTemplate(templ) + re := captureOutput(func() { + SetMode(DebugMode) + router.SetHTMLTemplate(templ) + SetMode(TestMode) + }) - assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", b.String()) + assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", re) c.HTML(http.StatusCreated, "t", H{"name": "alexandernyquist"}) diff --git a/debug.go b/debug.go index 693486a..d6d8bce 100644 --- a/debug.go +++ b/debug.go @@ -6,14 +6,10 @@ package gin import ( "bytes" + "fmt" "html/template" - "log" ) -func init() { - log.SetFlags(0) -} - // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { @@ -48,7 +44,7 @@ func debugPrintLoadTemplate(tmpl *template.Template) { func debugPrint(format string, values ...interface{}) { if IsDebugging() { - log.Printf("[GIN-debug] "+format, values...) + fmt.Printf("[GIN-debug] "+format, values...) } } diff --git a/debug_test.go b/debug_test.go index ed5a6a5..0b9d791 100644 --- a/debug_test.go +++ b/debug_test.go @@ -11,6 +11,7 @@ import ( "io" "log" "os" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -30,86 +31,101 @@ func TestIsDebugging(t *testing.T) { } func TestDebugPrint(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - SetMode(ReleaseMode) - debugPrint("DEBUG this!") - SetMode(TestMode) - debugPrint("DEBUG this!") - assert.Empty(t, w.String()) - - SetMode(DebugMode) - debugPrint("these are %d %s\n", 2, "error messages") - assert.Equal(t, "[GIN-debug] these are 2 error messages\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + SetMode(ReleaseMode) + debugPrint("DEBUG this!") + SetMode(TestMode) + debugPrint("DEBUG this!") + SetMode(DebugMode) + debugPrint("these are %d %s\n", 2, "error messages") + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re) } func TestDebugPrintError(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - SetMode(DebugMode) - debugPrintError(nil) - assert.Empty(t, w.String()) - - debugPrintError(errors.New("this is an error")) - assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintError(nil) + debugPrintError(errors.New("this is an error")) + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] [ERROR] this is an error\n", re) } func TestDebugPrintRoutes(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) - assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) + SetMode(TestMode) + }) + assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re) } func TestDebugPrintLoadTemplate(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) - debugPrintLoadTemplate(templ) - assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) + debugPrintLoadTemplate(templ) + SetMode(TestMode) + }) + assert.Regexp(t, `^\[GIN-debug\] Loaded HTML Templates \(2\): \n(\t- \n|\t- hello\.tmpl\n){2}\n`, re) } func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - debugPrintWARNINGSetHTMLTemplate() - assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintWARNINGSetHTMLTemplate() + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", re) } func TestDebugPrintWARNINGDefault(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - debugPrintWARNINGDefault() - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintWARNINGDefault() + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } func TestDebugPrintWARNINGNew(t *testing.T) { - var w bytes.Buffer - setup(&w) - defer teardown() - - debugPrintWARNINGNew() - assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", w.String()) + re := captureOutput(func() { + SetMode(DebugMode) + debugPrintWARNINGNew() + SetMode(TestMode) + }) + assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re) } -func setup(w io.Writer) { - SetMode(DebugMode) - log.SetOutput(w) -} - -func teardown() { - SetMode(TestMode) - log.SetOutput(os.Stdout) +func captureOutput(f func()) string { + reader, writer, err := os.Pipe() + if err != nil { + panic(err) + } + stdout := os.Stdout + stderr := os.Stderr + defer func() { + os.Stdout = stdout + os.Stderr = stderr + log.SetOutput(os.Stderr) + }() + os.Stdout = writer + os.Stderr = writer + log.SetOutput(writer) + out := make(chan string) + wg := new(sync.WaitGroup) + wg.Add(1) + go func() { + var buf bytes.Buffer + wg.Done() + io.Copy(&buf, reader) + out <- buf.String() + }() + wg.Wait() + f() + writer.Close() + return <-out } From c617b6241a1891081b3632ed89c1887dc4c353af Mon Sep 17 00:00:00 2001 From: Javier Provecho Fernandez Date: Thu, 20 Sep 2018 03:13:04 +0200 Subject: [PATCH 224/912] chore: recover go master build, partial revert #1514 (#1561) * chore: recover go master build, partial revert #1514 * chore: add master to go branch build targets --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index a93458f..398c140 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,11 @@ go: - 1.9.x - 1.10.x - 1.11.x + - master + +matrix: + allow_failures: + - go: master git: depth: 10 From f2cd3fcb2a9b4b108f5ce34a9f121ec13ac1cc16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 20 Sep 2018 11:53:58 +0800 Subject: [PATCH 225/912] chore: fix typo and add a little anotation (#1562) --- debug.go | 1 + routergroup.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/debug.go b/debug.go index d6d8bce..ce908dc 100644 --- a/debug.go +++ b/debug.go @@ -16,6 +16,7 @@ func IsDebugging() bool { return ginMode == debugCode } +// DebugPrintRouteFunc indicates debug log output format. var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { diff --git a/routergroup.go b/routergroup.go index be561de..579aa7d 100644 --- a/routergroup.go +++ b/routergroup.go @@ -17,7 +17,7 @@ type IRouter interface { Group(string, ...HandlerFunc) *RouterGroup } -// Iroutes defins all router handle interface. +// IRoutes defines all router handle interface. type IRoutes interface { Use(...HandlerFunc) IRoutes From a210eea3bd1c3766d76968108dfcd83c331f549c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 21 Sep 2018 10:21:59 +0800 Subject: [PATCH 226/912] improve panic information when a catch-all wildcard conflict occurs (#1529) --- tree.go | 11 +++++++++-- tree_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index b653066..ada62ce 100644 --- a/tree.go +++ b/tree.go @@ -193,9 +193,16 @@ func (n *node) addRoute(path string, handlers HandlersChain) { } } - panic("path segment '" + path + + pathSeg := path + if n.nType != catchAll { + pathSeg = strings.SplitN(path, "/", 2)[0] + } + prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path + panic("'" + pathSeg + + "' in new path '" + fullPath + "' conflicts with existing wildcard '" + n.path + - "' in path '" + fullPath + "'") + "' in existing prefix '" + prefix + + "'") } c := path[0] diff --git a/tree_test.go b/tree_test.go index 152f633..a1b3bbe 100644 --- a/tree_test.go +++ b/tree_test.go @@ -5,7 +5,9 @@ package gin import ( + "fmt" "reflect" + "regexp" "strings" "testing" ) @@ -653,3 +655,43 @@ func TestTreeInvalidNodeType(t *testing.T) { t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv) } } + +func TestTreeWildcardConflictEx(t *testing.T) { + conflicts := [...]struct { + route string + segPath string + existPath string + existSegPath string + }{ + {"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`}, + {"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`}, + {"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`}, + {"/conxxx", "xxx", `/con:tact`, `:tact`}, + {"/conooo/xxx", "ooo", `/con:tact`, `:tact`}, + } + + for _, conflict := range conflicts { + // I have to re-create a 'tree', because the 'tree' will be + // in an inconsistent state when the loop recovers from the + // panic which threw by 'addRoute' function. + tree := &node{} + routes := [...]string{ + "/con:tact", + "/who/are/*you", + "/who/foo/hello", + } + + for _, route := range routes { + tree.addRoute(route, fakeHandler(route)) + } + + recv := catchPanic(func() { + tree.addRoute(conflict.route, fakeHandler(conflict.route)) + }) + + if !regexp.MustCompile(fmt.Sprintf("'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'", + conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) { + t.Fatalf("invalid wildcard conflict error (%v)", recv) + } + } +} From 5a75dc712705510ab39fe7c448e7ab11459ce5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 22 Sep 2018 11:37:28 +0800 Subject: [PATCH 227/912] add release badge for readme (#1533) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 45bfce5..6fd2dd3 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) +[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) 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. From ad53619b15fda20df57c16cf19fe83f4a428cefa Mon Sep 17 00:00:00 2001 From: Dustin Decker Date: Sun, 23 Sep 2018 00:15:23 -0700 Subject: [PATCH 228/912] Don't log requests (#1370) Fixes #1331 HTTP logging leaks sensitive request information. This PR removes HTTP request logging during panics. --- recovery.go | 8 ++++++-- recovery_test.go | 12 +++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/recovery.go b/recovery.go index 61c5bd5..6c28b4f 100644 --- a/recovery.go +++ b/recovery.go @@ -39,8 +39,12 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { if err := recover(); err != nil { if logger != nil { stack := stack(3) - httprequest, _ := httputil.DumpRequest(c.Request, false) - logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) + if IsDebugging() { + httprequest, _ := httputil.DumpRequest(c.Request, false) + logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) + } else { + logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", timeFormat(time.Now()), err, stack, reset) + } } c.AbortWithStatus(http.StatusInternalServerError) } diff --git a/recovery_test.go b/recovery_test.go index 53f4a07..7d422b7 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -24,9 +24,19 @@ func TestPanicInHandler(t *testing.T) { w := performRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) - assert.Contains(t, buffer.String(), "GET /recovery") + assert.Contains(t, buffer.String(), "panic recovered") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") assert.Contains(t, buffer.String(), "TestPanicInHandler") + assert.NotContains(t, buffer.String(), "GET /recovery") + + // Debug mode prints the request + SetMode(DebugMode) + // RUN + w = performRequest(router, "GET", "/recovery") + // TEST + assert.Equal(t, http.StatusInternalServerError, w.Code) + assert.Contains(t, buffer.String(), "GET /recovery") + } // TestPanicWithAbort assert that panic has been recovered even if context.Abort was used. From b02e4f2ed68e1bd9598b8eced0b21c9aacfed5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 25 Sep 2018 21:52:21 +0800 Subject: [PATCH 229/912] ci: fast finish when build failed (#1568) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 398c140..f9e241d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ go: matrix: allow_failures: - go: master + fast_finish: true git: depth: 10 From fd599fcceadba48be39ded04d1e2fb3181b43e8c Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 25 Sep 2018 21:28:25 -0500 Subject: [PATCH 230/912] Make logger use a yellow background and a darkgray text for legibility (#1570) 1. Why is this change neccesary? White text on a yellow background was illegible with most terminal color schemes 2. How does it address the issue? The white text was replaced with a bash compatible dark gray while keeping the yellow background colour 3. What side effects does this change have? Resolves #1552 --- logger.go | 2 +- logger_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/logger.go b/logger.go index 1a8df60..5f98685 100644 --- a/logger.go +++ b/logger.go @@ -17,7 +17,7 @@ import ( var ( green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) + yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) diff --git a/logger_test.go b/logger_test.go index 7dbbf7b..6118cb0 100644 --- a/logger_test.go +++ b/logger_test.go @@ -85,7 +85,7 @@ func TestLogger(t *testing.T) { func TestColorForMethod(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") + assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta") @@ -96,7 +96,7 @@ func TestColorForMethod(t *testing.T) { func TestColorForStatus(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") + assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") } From 834a2ec64ca1ce077e4bdeb5edec019df1c01e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 26 Sep 2018 13:49:11 +0800 Subject: [PATCH 231/912] feat: add go version judge when print debug warning log (#1572) * feat: add go version judge when print debug warning log * remove invalid statement * use one const --- debug.go | 18 +++++++++++++++++- debug_test.go | 24 +++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/debug.go b/debug.go index ce908dc..4fd23fa 100644 --- a/debug.go +++ b/debug.go @@ -8,8 +8,13 @@ import ( "bytes" "fmt" "html/template" + "runtime" + "strconv" + "strings" ) +const ginSupportMinGoVer = 6 + // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { @@ -49,10 +54,21 @@ func debugPrint(format string, values ...interface{}) { } } +func getMinVer(v string) (uint64, error) { + first := strings.IndexByte(v, '.') + last := strings.LastIndexByte(v, '.') + if first == last { + return strconv.ParseUint(v[first+1:], 10, 64) + } + return strconv.ParseUint(v[first+1:last], 10, 64) +} + func debugPrintWARNINGDefault() { - debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { + debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. `) + } debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. `) diff --git a/debug_test.go b/debug_test.go index 0b9d791..c8a010f 100644 --- a/debug_test.go +++ b/debug_test.go @@ -11,6 +11,7 @@ import ( "io" "log" "os" + "runtime" "sync" "testing" @@ -88,7 +89,13 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { debugPrintWARNINGDefault() SetMode(TestMode) }) - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + m, e := getMinVer(runtime.Version()) + assert.Nil(t, e) + if m <= ginSupportMinGoVer { + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + } else { + assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + } } func TestDebugPrintWARNINGNew(t *testing.T) { @@ -129,3 +136,18 @@ func captureOutput(f func()) string { writer.Close() return <-out } + +func TestGetMinVer(t *testing.T) { + var m uint64 + var e error + _, e = getMinVer("go1") + assert.NotNil(t, e) + m, e = getMinVer("go1.1") + assert.Equal(t, uint64(1), m) + assert.Nil(t, e) + m, e = getMinVer("go1.1.1") + assert.Nil(t, e) + assert.Equal(t, uint64(1), m) + _, e = getMinVer("go1.1.1.1") + assert.NotNil(t, e) +} From 402ef120e19218544152502d1ea20709ff5adbc8 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 27 Sep 2018 08:59:44 +0800 Subject: [PATCH 232/912] fix: fmt output log to os.Stderr (#1571) fix #1560 changes are breaking in App Engine. cc @giulianobr @philippgille --- debug.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debug.go b/debug.go index 4fd23fa..c5e65b2 100644 --- a/debug.go +++ b/debug.go @@ -8,6 +8,7 @@ import ( "bytes" "fmt" "html/template" + "os" "runtime" "strconv" "strings" @@ -50,7 +51,7 @@ func debugPrintLoadTemplate(tmpl *template.Template) { func debugPrint(format string, values ...interface{}) { if IsDebugging() { - fmt.Printf("[GIN-debug] "+format, values...) + fmt.Fprintf(os.Stderr, "[GIN-debug] "+format, values...) } } From b7314d943c81052dd7155499c7313d7524cb024c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 27 Sep 2018 09:42:41 +0800 Subject: [PATCH 233/912] chore: fix debug test error (#1574) --- debug_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/debug_test.go b/debug_test.go index c8a010f..97ff166 100644 --- a/debug_test.go +++ b/debug_test.go @@ -90,8 +90,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { SetMode(TestMode) }) m, e := getMinVer(runtime.Version()) - assert.Nil(t, e) - if m <= ginSupportMinGoVer { + if e == nil && m <= ginSupportMinGoVer { assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) From 91a4459dd27a311c2b959708f328d60177fa4046 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 27 Sep 2018 09:58:47 +0800 Subject: [PATCH 234/912] remove allow fail flag (#1573) golang team revert the net/url issue: https://github.com/golang/go/issues/27302 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f9e241d..a765f37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,6 @@ go: - master matrix: - allow_failures: - - go: master fast_finish: true git: From e9f187f60a27477e54c177f314db00d6a1abf062 Mon Sep 17 00:00:00 2001 From: James Pettyjohn Date: Sun, 30 Sep 2018 19:49:39 -0700 Subject: [PATCH 235/912] removed use of sync.pool from HandleContext and added test coverage (#1565) As per #1230 there is an issue when using HandleContext where the context of the request is returned to the context sync.Pool before the parent request has finished, causing context to be used in a non-thread safe manner. I've removed the bug by not entering the context back in the pool and leaving that to ServeHTTP. There was no test coverage for this function so I've also added the test to cover it. As the bug only happens when there are concurrent requests, the tests issues hundreds of concurrent requests. Without the bug fixed the tests do consistently recreate the error. --- gin.go | 1 - gin_integration_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 11f6d94..92c24ba 100644 --- a/gin.go +++ b/gin.go @@ -336,7 +336,6 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (engine *Engine) HandleContext(c *Context) { c.reset() engine.handleHTTPRequest(c) - engine.pool.Put(c) } func (engine *Engine) handleHTTPRequest(c *Context) { diff --git a/gin_integration_test.go b/gin_integration_test.go index 52f7884..12a943b 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -12,6 +12,7 @@ import ( "net/http" "net/http/httptest" "os" + "sync" "testing" "time" @@ -119,6 +120,29 @@ func TestWithHttptestWithAutoSelectedPort(t *testing.T) { testRequest(t, ts.URL+"/example") } +func TestConcurrentHandleContext(t *testing.T) { + router := New() + router.GET("/", func(c *Context) { + c.Request.URL.Path = "/example" + router.HandleContext(c) + }) + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + + ts := httptest.NewServer(router) + defer ts.Close() + + var wg sync.WaitGroup + iterations := 200 + wg.Add(iterations) + for i := 0; i < iterations; i++ { + go func() { + testRequest(t, ts.URL+"/") + wg.Done() + }() + } + wg.Wait() +} + // func TestWithHttptestWithSpecifiedPort(t *testing.T) { // router := New() // router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) From fbdcbd22751c7174d4f363995fca296e4670726b Mon Sep 17 00:00:00 2001 From: zesani <7sin@outlook.co.th> Date: Tue, 9 Oct 2018 06:14:21 +0700 Subject: [PATCH 236/912] Update README.md (#1583) change "hava" to "have" --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fd2dd3..95bd320 100644 --- a/README.md +++ b/README.md @@ -1721,11 +1721,11 @@ type StructX struct { } type StructY struct { - Y StructX `form:"name_y"` // HERE hava form + Y StructX `form:"name_y"` // HERE have form } type StructZ struct { - Z *StructZ `form:"name_z"` // HERE hava form + Z *StructZ `form:"name_z"` // HERE have form } ``` From 6ab50f944ca52bdd4d982d0bec454d94bf7b802a Mon Sep 17 00:00:00 2001 From: andriikushch Date: Fri, 12 Oct 2018 01:31:31 +0200 Subject: [PATCH 237/912] replace deprecated HeaderMap with Header() (#1585) --- auth_test.go | 4 +-- context_17_test.go | 2 +- context_test.go | 64 +++++++++++++++++++++---------------------- render/render_test.go | 6 ++-- routes_test.go | 12 ++++---- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/auth_test.go b/auth_test.go index ab7e94b..197e920 100644 --- a/auth_test.go +++ b/auth_test.go @@ -122,7 +122,7 @@ func TestBasicAuth401(t *testing.T) { assert.False(t, called) assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Equal(t, "Basic realm=\"Authorization Required\"", w.HeaderMap.Get("WWW-Authenticate")) + assert.Equal(t, "Basic realm=\"Authorization Required\"", w.Header().Get("WWW-Authenticate")) } func TestBasicAuth401WithCustomRealm(t *testing.T) { @@ -142,5 +142,5 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { assert.False(t, called) assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.HeaderMap.Get("WWW-Authenticate")) + assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate")) } diff --git a/context_17_test.go b/context_17_test.go index d225190..5b9ebcd 100644 --- a/context_17_test.go +++ b/context_17_test.go @@ -23,5 +23,5 @@ func TestContextRenderPureJSON(t *testing.T) { c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/context_test.go b/context_test.go index 5a5bb6e..2e972f1 100644 --- a/context_test.go +++ b/context_test.go @@ -628,7 +628,7 @@ func TestContextRenderJSON(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSONP @@ -642,7 +642,7 @@ func TestContextRenderJSONP(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) - assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSONP @@ -656,7 +656,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no JSON is rendered if code is 204 @@ -668,7 +668,7 @@ func TestContextRenderNoContentJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSON @@ -682,7 +682,7 @@ func TestContextRenderAPIJSON(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) - assert.Equal(t, "application/vnd.api+json", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 @@ -695,7 +695,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/vnd.api+json") + assert.Equal(t, w.Header().Get("Content-Type"), "application/vnd.api+json") } // Tests that the response is serialized as JSON @@ -708,7 +708,7 @@ func TestContextRenderIndentedJSON(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\",\n \"nested\": {\n \"foo\": \"bar\"\n }\n}", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 @@ -720,7 +720,7 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that the response is serialized as Secure JSON @@ -734,7 +734,7 @@ func TestContextRenderSecureJSON(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "&&&START&&&[\"foo\",\"bar\"]", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no Custom JSON is rendered if code is 204 @@ -746,7 +746,7 @@ func TestContextRenderNoContentSecureJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextRenderNoContentAsciiJSON(t *testing.T) { @@ -757,7 +757,7 @@ func TestContextRenderNoContentAsciiJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/json", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json", w.Header().Get("Content-Type")) } // Tests that the response executes the templates @@ -773,7 +773,7 @@ func TestContextRenderHTML(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextRenderHTML2(t *testing.T) { @@ -797,7 +797,7 @@ func TestContextRenderHTML2(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no HTML is rendered if code is 204 @@ -811,7 +811,7 @@ func TestContextRenderNoContentHTML(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextXML tests that the response is serialized as XML @@ -824,7 +824,7 @@ func TestContextRenderXML(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "bar", w.Body.String()) - assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no XML is rendered if code is 204 @@ -836,7 +836,7 @@ func TestContextRenderNoContentXML(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextString tests that the response is returned @@ -849,7 +849,7 @@ func TestContextRenderString(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "test string 2", w.Body.String()) - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no String is rendered if code is 204 @@ -861,7 +861,7 @@ func TestContextRenderNoContentString(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextString tests that the response is returned @@ -875,7 +875,7 @@ func TestContextRenderHTMLString(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "string 3", w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // Tests that no HTML String is rendered if code is 204 @@ -888,7 +888,7 @@ func TestContextRenderNoContentHTMLString(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextData tests that the response can be written from `bytesting` @@ -901,7 +901,7 @@ func TestContextRenderData(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo,bar", w.Body.String()) - assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/csv", w.Header().Get("Content-Type")) } // Tests that no Custom Data is rendered if code is 204 @@ -913,7 +913,7 @@ func TestContextRenderNoContentData(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, "text/csv", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/csv", w.Header().Get("Content-Type")) } func TestContextRenderSSE(t *testing.T) { @@ -942,7 +942,7 @@ func TestContextRenderFile(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextRenderYAML tests that the response is serialized as YAML @@ -955,7 +955,7 @@ func TestContextRenderYAML(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf @@ -979,7 +979,7 @@ func TestContextRenderProtoBuf(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, string(protoData), w.Body.String()) - assert.Equal(t, "application/x-protobuf", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } func TestContextHeaders(t *testing.T) { @@ -1062,7 +1062,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationWithXML(t *testing.T) { @@ -1077,7 +1077,7 @@ func TestContextNegotiationWithXML(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "bar", w.Body.String()) - assert.Equal(t, "application/xml; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationWithHTML(t *testing.T) { @@ -1095,7 +1095,7 @@ func TestContextNegotiationWithHTML(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Hello gin", w.Body.String()) - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationNotSupport(t *testing.T) { @@ -1131,7 +1131,7 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) { assert.Empty(t, c.NegotiateFormat(MIMEJSON)) } -func TestContextNegotiationFormatCustum(t *testing.T) { +func TestContextNegotiationFormatCustom(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") @@ -1627,9 +1627,9 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, body, w.Body.String()) - assert.Equal(t, contentType, w.HeaderMap.Get("Content-Type")) - assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) - assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) + assert.Equal(t, contentType, w.Header().Get("Content-Type")) + assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length")) + assert.Equal(t, extraHeaders["Content-Disposition"], w.Header().Get("Content-Disposition")) } type TestResponseRecorder struct { diff --git a/render/render_test.go b/render/render_test.go index 09ccc65..4c9b180 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -480,7 +480,7 @@ func TestRenderReader(t *testing.T) { assert.NoError(t, err) assert.Equal(t, body, w.Body.String()) - assert.Equal(t, "image/png", w.HeaderMap.Get("Content-Type")) - assert.Equal(t, strconv.Itoa(len(body)), w.HeaderMap.Get("Content-Length")) - assert.Equal(t, headers["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) + assert.Equal(t, "image/png", w.Header().Get("Content-Type")) + assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length")) + assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) } diff --git a/routes_test.go b/routes_test.go index 23e749e..60f1c81 100644 --- a/routes_test.go +++ b/routes_test.go @@ -267,7 +267,7 @@ func TestRouteStaticFile(t *testing.T) { assert.Equal(t, w, w2) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Gin Web Framework", w.Body.String()) - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) w3 := performRequest(router, "HEAD", "/using_static/"+filename) w4 := performRequest(router, "HEAD", "/result") @@ -285,7 +285,7 @@ func TestRouteStaticListingDir(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "gin.go") - assert.Equal(t, "text/html; charset=utf-8", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } // TestHandleHeadToDir - ensure the root/sub dir handles properly @@ -312,10 +312,10 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") - assert.Equal(t, "text/plain; charset=utf-8", w.HeaderMap.Get("Content-Type")) - assert.NotEqual(t, w.HeaderMap.Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") - assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.HeaderMap.Get("Expires")) - assert.Equal(t, "Gin Framework", w.HeaderMap.Get("x-GIN")) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") + assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) + assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) } func TestRouteNotAllowedEnabled(t *testing.T) { From 268e30710b77e1cd48b25df235cf702dc8e942a2 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 14 Oct 2018 11:05:24 +0800 Subject: [PATCH 238/912] fix(Makefile): golint to new URL (#1592) as title. Just update the golint to new URL. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 51b9969..ea7b4f7 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ embedmd: .PHONY: lint lint: @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/golang/lint/golint; \ + go get -u golang.org/x/lint/golint; \ fi for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; From 01ca2530d4b6c44c718b00c06f0e1d092572d49e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 14 Oct 2018 12:39:16 +0800 Subject: [PATCH 239/912] refactor(Makefile): allow overriding default go program (#1593) --- Makefile | 28 ++++++++++++++++++---------- coverage.sh | 13 ------------- 2 files changed, 18 insertions(+), 23 deletions(-) delete mode 100644 coverage.sh diff --git a/Makefile b/Makefile index ea7b4f7..c20429a 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ +GO ?= go GOFMT ?= gofmt "-s" -PACKAGES ?= $(shell go list ./... | grep -v /vendor/) -VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/) +PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/) +VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/ | grep -v /examples/) GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") +TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples) all: install @@ -10,7 +12,14 @@ install: deps .PHONY: test test: - sh coverage.sh + echo "mode: count" > coverage.out + for d in $(TESTFOLDER); do \ + $(GO) test -v -covermode=count -coverprofile=profile.out $$d; \ + if [ -f profile.out ]; then \ + cat profile.out | grep -v "mode:" >> coverage.out; \ + rm profile.out; \ + fi; \ + done .PHONY: fmt fmt: @@ -18,7 +27,6 @@ fmt: .PHONY: fmt-check fmt-check: - # get all go files and run go fmt on them @diff=$$($(GOFMT) -d $(GOFILES)); \ if [ -n "$$diff" ]; then \ echo "Please run 'make fmt' and commit the result:"; \ @@ -27,14 +35,14 @@ fmt-check: fi; vet: - go vet $(VETPACKAGES) + $(GO) vet $(VETPACKAGES) deps: @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/kardianos/govendor; \ + $(GO) get -u github.com/kardianos/govendor; \ fi @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/campoy/embedmd; \ + $(GO) get -u github.com/campoy/embedmd; \ fi embedmd: @@ -43,20 +51,20 @@ embedmd: .PHONY: lint lint: @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u golang.org/x/lint/golint; \ + $(GO) get -u golang.org/x/lint/golint; \ fi for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; .PHONY: misspell-check misspell-check: @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/client9/misspell/cmd/misspell; \ + $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi misspell -error $(GOFILES) .PHONY: misspell misspell: @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go get -u github.com/client9/misspell/cmd/misspell; \ + $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi misspell -w $(GOFILES) diff --git a/coverage.sh b/coverage.sh deleted file mode 100644 index 4d1ee03..0000000 --- a/coverage.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -e - -echo "mode: count" > coverage.out - -for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do - go test -v -covermode=count -coverprofile=profile.out $d - if [ -f profile.out ]; then - cat profile.out | grep -v "mode:" >> coverage.out - rm profile.out - fi -done From 523435e5245faade0a43a33bd3faab32456bab32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 15 Oct 2018 12:52:51 +0800 Subject: [PATCH 240/912] attempt to support go module (#1569) * support go module * update golint package url * update golint --- .travis.yml | 10 +++++++++- Makefile | 6 ++++++ go.mod | 13 ++++++++----- go.sum | 26 ++++++++++++++++---------- tools.go | 25 +++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 tools.go diff --git a/.travis.yml b/.travis.yml index a765f37..2eeb0b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,12 +11,20 @@ go: matrix: fast_finish: true + include: + - go: 1.11.x + env: GO111MODULE=on git: depth: 10 +before_install: + - if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi + install: - - make install + - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; else make install; fi + - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi + - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi go_import_path: github.com/gin-gonic/gin diff --git a/Makefile b/Makefile index c20429a..b698ac0 100644 --- a/Makefile +++ b/Makefile @@ -68,3 +68,9 @@ misspell: $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi misspell -w $(GOFILES) + +.PHONY: tools +tools: + go install golang.org/x/lint/golint; \ + go install github.com/client9/misspell/cmd/misspell; \ + go install github.com/campoy/embedmd; diff --git a/go.mod b/go.mod index bd4ad97..0797b93 100644 --- a/go.mod +++ b/go.mod @@ -1,25 +1,28 @@ module github.com/gin-gonic/gin require ( - github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 // indirect + github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 + github.com/client9/misspell v0.3.4 + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b github.com/golang/protobuf v1.2.0 github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 - github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b + github.com/json-iterator/go v1.1.5 github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 github.com/mattn/go-isatty v0.0.3 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 - github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6 + github.com/thinkerou/favicon v0.1.0 github.com/ugorji/go v1.1.1 - golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b + golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 + golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f - golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e // indirect + golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect google.golang.org/grpc v1.15.0 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 diff --git a/go.sum b/go.sum index 3382bee..2d307e7 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 h1:tRsilif6pbtt+PX6uRoyGd+qR+4ZPucFZLHlc3Ak6z8= +github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296/go.mod h1:/dBk8ICkslPCmyRdn4azP+QvBxL6Eg3EYxUGI9xMMFw= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16 h1:HqkufMBR7waVfFFRABWqHa1WgTvjtVDJTLJe3CR576A= -github.com/davecgh/go-spew v0.0.0-20180221232628-8991bc29aa16/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 h1:QnnoVdChKs+GeTvN4rPYTW6b5U6M3HMEvQ/+x4IGtfY= github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66/go.mod h1:kTEh6M2J/mh7nsskr28alwLCXm/DSG5OSA/o31yy2XU= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= @@ -10,14 +13,15 @@ github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8J github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 h1:cW/amwGEJK5MSKntPXRjX4dxs/nGxGT8gXKIsKFmHGc= github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15/go.mod h1:Fdm/oWRW+CH8PRbLntksCNtmcCBximKPkVQYvmMl80k= -github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b h1:X61dhFTE1Au92SvyF8HyAwdjWqiSdfBgFR7wTxC0+uU= -github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= @@ -31,12 +35,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6 h1:d/LEgOfWe+AlOCz/kzmkvlO+gq9LRGhjSHqt2nx8Muc= -github.com/thinkerou/favicon v0.0.0-20170710140520-94a442a49da6/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= +github.com/thinkerou/favicon v0.1.0 h1:eWMISKTpHq2G8HOuKn7ydD55j5DDehx94b0C2y8ABMs= +github.com/thinkerou/favicon v0.1.0/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ= -golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 h1:00BeQWmeaGazuOrq8Q5K5d3/cHaGuFrZzpaHBXfrsUA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -44,10 +49,11 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..9f96406 --- /dev/null +++ b/tools.go @@ -0,0 +1,25 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build tools + +// This file exists to cause `go mod` and `go get` to believe these tools +// are dependencies, even though they are not runtime dependencies of any +// gin package. This means they will appear in `go.mod` file, but will not +// be a part of the build. + +package gin + +import ( + _ "github.com/campoy/embedmd" + _ "github.com/client9/misspell/cmd/misspell" + _ "github.com/dustin/go-broadcast" + _ "github.com/gin-gonic/autotls" + _ "github.com/jessevdk/go-assets" + _ "github.com/manucorporat/stats" + _ "github.com/thinkerou/favicon" + _ "golang.org/x/crypto/acme/autocert" + _ "golang.org/x/lint/golint" + _ "google.golang.org/grpc" +) From 98082fd590798eda8bdada82fd1550dfe6941964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 15 Oct 2018 13:01:44 +0800 Subject: [PATCH 241/912] document: add docs dir and middleware document (#1521) * init docs dir * add middleware document * fix indent * update docs --- docs/how-to-build-an-effective-middleware.md | 137 +++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/how-to-build-an-effective-middleware.md diff --git a/docs/how-to-build-an-effective-middleware.md b/docs/how-to-build-an-effective-middleware.md new file mode 100644 index 0000000..db04428 --- /dev/null +++ b/docs/how-to-build-an-effective-middleware.md @@ -0,0 +1,137 @@ +# How to build one effective middleware? + +## Consitituent part + +The middleware has two parts: + + - part one is what is executed once, when you initalize your middleware. That's where you set up all the global objects, logicals etc. Everything that happens one per application lifetime. + + - part two is what executes on every request. For example, a database middleware you simply inject your "global" database object into the context. Once it's inside the context, you can retrieve it from within other middlewares and your handler furnction. + +```go +func funcName(params string) gin.HandlerFunc { + // <--- + // This is part one + // ---> + // The follow code is an example + if err := check(params); err != nil { + panic(err) + } + + return func(c *gin.Context) { + // <--- + // This is part two + // ---> + // The follow code is an example + c.Set("TestVar", params) + c.Next() + } +} +``` + +## Execution process + +Firstly, we have the follow example code: + +```go +func main() { + router := gin.Default() + + router.Use(globalMiddleware()) + + router.GET("/rest/n/api/*some", mid1(), mid2(), handler) + + router.Run() +} + +func globalMiddleware() gin.HandlerFunc { + fmt.Println("globalMiddleware...1") + + return func(c *gin.Context) { + fmt.Println("globalMiddleware...2") + c.Next() + fmt.Println("globalMiddleware...3") + } +} + +func handler(c *gin.Context) { + fmt.Println("exec handler.") +} + +func mid1() gin.HandlerFunc { + fmt.Println("mid1...1") + + return func(c *gin.Context) { + + fmt.Println("mid1...2") + c.Next() + fmt.Println("mid1...3") + } +} + +func mid2() gin.HandlerFunc { + fmt.Println("mid2...1") + + return func(c *gin.Context) { + fmt.Println("mid2...2") + c.Next() + fmt.Println("mid2...3") + } +} +``` + +According to [Consitituent part](#consitituent-part) said, when we run the gin process, **part one** will execute firstly and will print the follow information: + +```go +globalMiddleware...1 +mid1...1 +mid2...1 +``` + +And init order are: + +```go +globalMiddleware...1 + | + v +mid1...1 + | + v +mid2...1 +``` + +When we curl one request `curl -v localhost:8080/rest/n/api/some`, **part two** will execute their middleware and output the following information: + +```go +globalMiddleware...2 +mid1...2 +mid2...2 +exec handler. +mid2...3 +mid1...3 +globalMiddleware...3 +``` + +In other words, run order are: + +```go +globalMiddleware...2 + | + v +mid1...2 + | + v +mid2...2 + | + v +exec handler. + | + v +mid2...3 + | + v +mid1...3 + | + v +globalMiddleware...3 +``` From 524757b81c99e51a64e6ecc7ee0c181abc39bd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 15 Oct 2018 20:24:32 +0800 Subject: [PATCH 242/912] vendor: upgrade some dependency package version (#1596) ref https://github.com/gin-gonic/gin/pull/1569#issuecomment-429731722 --- go.mod | 10 +++++----- go.sum | 19 ++++++++++--------- vendor/vendor.json | 43 +++++++++++++++++++++++++------------------ 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/go.mod b/go.mod index 0797b93..ef4103f 100644 --- a/go.mod +++ b/go.mod @@ -11,18 +11,18 @@ require ( github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 github.com/json-iterator/go v1.1.5 github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 - github.com/mattn/go-isatty v0.0.3 + github.com/mattn/go-isatty v0.0.4 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 github.com/thinkerou/favicon v0.1.0 github.com/ugorji/go v1.1.1 - golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 - golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 - golang.org/x/net v0.0.0-20180826012351-8a410e7b638d + golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e + golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd + golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f - golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect + golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba // indirect google.golang.org/grpc v1.15.0 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 diff --git a/go.sum b/go.sum index 2d307e7..2ef7f13 100644 --- a/go.sum +++ b/go.sum @@ -13,7 +13,6 @@ github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8J github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= @@ -25,8 +24,8 @@ github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= -github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= @@ -39,18 +38,20 @@ github.com/thinkerou/favicon v0.1.0 h1:eWMISKTpHq2G8HOuKn7ydD55j5DDehx94b0C2y8AB github.com/thinkerou/favicon v0.1.0/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= -golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 h1:00BeQWmeaGazuOrq8Q5K5d3/cHaGuFrZzpaHBXfrsUA= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= +golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd h1:cgsAvzdqkDKdI02tIvDjO225vDPHMDCgfKqx5KEVI7U= +golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0= -golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc= +golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs= diff --git a/vendor/vendor.json b/vendor/vendor.json index 86df11b..af1a014 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -6,7 +6,9 @@ "checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=", "path": "github.com/davecgh/go-spew/spew", "revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73", - "revisionTime": "2018-02-21T22:46:20Z" + "revisionTime": "2018-02-21T22:46:20Z", + "version": "v1.1", + "versionExact": "v1.1.1" }, { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", @@ -15,12 +17,12 @@ "revisionTime": "2017-01-09T09:34:21Z" }, { - "checksumSHA1": "Pyou8mceOASSFxc7GeXZuVdSMi0=", + "checksumSHA1": "mE9XW26JSpe4meBObM6J/Oeq0eg=", "path": "github.com/golang/protobuf/proto", - "revision": "b4deda0973fb4c70b50d226b1af49f3da59f5265", - "revisionTime": "2018-04-30T18:52:41Z", - "version": "v1.1.0", - "versionExact": "v1.1.0" + "revision": "aa810b61a9c79d51363740d207bb46cf8e620ed5", + "revisionTime": "2018-08-14T21:14:27Z", + "version": "v1.2", + "versionExact": "v1.2.0" }, { "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=", @@ -31,19 +33,19 @@ "versionExact": "v1.1.5" }, { - "checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=", + "checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=", "path": "github.com/mattn/go-isatty", - "revision": "0360b2af4f38e8d38c7fce2a9f4e702702d73a39", - "revisionTime": "2017-09-25T05:34:41Z", - "version": "v0.0.3", - "versionExact": "v0.0.3" + "revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c", + "revisionTime": "2017-11-07T05:05:31Z", + "version": "v0.0", + "versionExact": "v0.0.4" }, { "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", "revisionTime": "2018-05-06T18:05:49Z", - "version": "v1.2.2", + "version": "v1.2", "versionExact": "v1.2.2" }, { @@ -51,15 +53,20 @@ "path": "github.com/ugorji/go/codec", "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab", "revisionTime": "2018-04-07T10:07:33Z", - "version": "v1.1.1", + "version": "v1.1", "versionExact": "v1.1.1" }, { - "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", - "comment": "release-branch.go1.7", + "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", "path": "golang.org/x/net/context", - "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", - "revisionTime": "2016-10-18T08:54:36Z" + "revision": "49bb7cea24b1df9410e1712aa6433dae904ff66a", + "revisionTime": "2018-10-11T05:27:23Z" + }, + { + "checksumSHA1": "SiJNkx+YGtq3Gtr6Ldu6OW83O+U=", + "path": "golang.org/x/sys/unix", + "revision": "fa43e7bc11baaae89f3f902b2b4d832b68234844", + "revisionTime": "2018-10-11T14:35:51Z" }, { "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", @@ -74,7 +81,7 @@ "path": "gopkg.in/yaml.v2", "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", "revisionTime": "2018-03-28T19:50:20Z", - "version": "v2.2.1", + "version": "v2.2", "versionExact": "v2.2.1" } ], From cfa092f4f045d004b4c03d5b69af8bab7ed5fc1b Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Tue, 16 Oct 2018 03:48:41 +0300 Subject: [PATCH 243/912] Fix LoadHTML* tests (#1559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Digging into the test code base I've found out that some of the tests for `LoadHTML*` methods are not reliable and efficient. They use timeouts to be sure that goroutine with the server has started. And even more, in old implementation, the server started only once – all the new instances silently failed due to the occupied network port. Here is a short overview of the proposed changes: - it's not necessary to rely on timeouts, the server starts listening synchronously and returns control when it is ready - once the server is run, it's stopped after a test passes - dry out http server setup - magic with empty closure return is eliminated - preserve router.RunTLS coverage with integration tests --- gin_integration_test.go | 26 +++- gin_test.go | 254 ++++++++++++++++++++++------------------ 2 files changed, 166 insertions(+), 114 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index 12a943b..038c8b7 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -6,6 +6,7 @@ package gin import ( "bufio" + "crypto/tls" "fmt" "io/ioutil" "net" @@ -20,7 +21,14 @@ import ( ) func testRequest(t *testing.T, url string) { - resp, err := http.Get(url) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{Transport: tr} + + resp, err := client.Get(url) assert.NoError(t, err) defer resp.Body.Close() @@ -45,6 +53,22 @@ func TestRunEmpty(t *testing.T) { testRequest(t, "http://localhost:8080/example") } +func TestRunTLS(t *testing.T) { + router := New() + go func() { + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + + assert.NoError(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + }() + + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + assert.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + testRequest(t, "https://localhost:8443/example") +} + func TestRunEmptyWithEnv(t *testing.T) { os.Setenv("PORT", "3123") router := New() diff --git a/gin_test.go b/gin_test.go index 95b9cc1..353c9be 100644 --- a/gin_test.go +++ b/gin_test.go @@ -10,6 +10,7 @@ import ( "html/template" "io/ioutil" "net/http" + "net/http/httptest" "reflect" "testing" "time" @@ -22,105 +23,105 @@ func formatAsDate(t time.Time) string { return fmt.Sprintf("%d/%02d/%02d", year, month, day) } -func setupHTMLFiles(t *testing.T, mode string, tls bool) func() { - go func() { - SetMode(mode) - router := New() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") - router.GET("/test", func(c *Context) { - c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) - }) - router.GET("/raw", func(c *Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) +func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server { + SetMode(mode) + router := New() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + loadMethod(router) + router.GET("/test", func(c *Context) { + c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) + }) + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) - if tls { - // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` - router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") - } else { - router.Run(":8888") - } - }() - t.Log("waiting 1 second for server startup") - time.Sleep(1 * time.Second) - return func() {} -} + }) -func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { - go func() { - SetMode(mode) - router := New() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLGlob("./testdata/template/*") - router.GET("/test", func(c *Context) { - c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) - }) - router.GET("/raw", func(c *Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - if tls { - // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` - router.RunTLS(":9999", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem") - } else { - router.Run(":8888") - } - }() - t.Log("waiting 1 second for server startup") - time.Sleep(1 * time.Second) - return func() {} + var ts *httptest.Server + + if tls { + ts = httptest.NewTLSServer(router) + } else { + ts = httptest.NewServer(router) + } + + return ts } -func TestLoadHTMLGlob(t *testing.T) { - td := setupHTMLGlob(t, DebugMode, false) - res, err := http.Get("http://127.0.0.1:8888/test") +func TestLoadHTMLGlobDebugMode(t *testing.T) { + ts := setupHTMLFiles( + t, + DebugMode, + false, + func(router *Engine) { + router.LoadHTMLGlob("./testdata/template/*") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

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

Hello world

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

Hello world

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

Hello world

", string(resp)) - - td() } func TestLoadHTMLGlobFromFuncMap(t *testing.T) { - time.Now() - td := setupHTMLGlob(t, DebugMode, false) - res, err := http.Get("http://127.0.0.1:8888/raw") + ts := setupHTMLFiles( + t, + DebugMode, + false, + func(router *Engine) { + router.LoadHTMLGlob("./testdata/template/*") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01\n", string(resp)) - - td() } func init() { @@ -164,59 +169,77 @@ func TestCreateEngine(t *testing.T) { assert.Empty(t, router.Handlers) } -// func TestLoadHTMLDebugMode(t *testing.T) { -// router := New() -// SetMode(DebugMode) -// router.LoadHTMLGlob("*.testtmpl") -// r := router.HTMLRender.(render.HTMLDebug) -// assert.Empty(t, r.Files) -// assert.Equal(t, "*.testtmpl", r.Glob) -// -// router.LoadHTMLFiles("index.html.testtmpl", "login.html.testtmpl") -// r = router.HTMLRender.(render.HTMLDebug) -// assert.Empty(t, r.Glob) -// assert.Equal(t, []string{"index.html", "login.html"}, r.Files) -// SetMode(TestMode) -// } - -func TestLoadHTMLFiles(t *testing.T) { - td := setupHTMLFiles(t, TestMode, false) - res, err := http.Get("http://127.0.0.1:8888/test") +func TestLoadHTMLFilesTestMode(t *testing.T) { + ts := setupHTMLFiles( + t, + TestMode, + false, + func(router *Engine) { + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "

Hello world

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

Hello world

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

Hello world

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

Hello world

", string(resp)) - td() } func TestLoadHTMLFilesFuncMap(t *testing.T) { - time.Now() - td := setupHTMLFiles(t, TestMode, false) - res, err := http.Get("http://127.0.0.1:8888/raw") + ts := setupHTMLFiles( + t, + TestMode, + false, + func(router *Engine) { + router.LoadHTMLFiles("./testdata/template/hello.tmpl", "./testdata/template/raw.tmpl") + }, + ) + defer ts.Close() + + res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { fmt.Println(err) } resp, _ := ioutil.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01\n", string(resp)) - - td() } func TestAddRoute(t *testing.T) { From 333bac5f94a1bd3237996fbcae6e78fee8dbc5c6 Mon Sep 17 00:00:00 2001 From: "A. F" Date: Wed, 17 Oct 2018 09:40:57 +0200 Subject: [PATCH 244/912] add example to set and get cookies (#1599) --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 95bd320..5c14c44 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [http2 server push](#http2-server-push) - [Define format for the log of routes](#define-format-for-the-log-of-routes) + - [Set and get a cookie](#set-and-get-a-cookie) - [Testing](#testing) - [Users](#users) @@ -1880,6 +1881,35 @@ func main() { } ``` +### Set and get a cookie + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { + + cookie, err := c.Cookie("gin_cookie") + + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } + + fmt.Printf("Cookie value: %s \n", cookie) + }) + + router.Run() +} +``` + ## Testing From a1a32562de0fe61b17aab42cd4fa7847ae814cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 19 Oct 2018 11:06:23 +0800 Subject: [PATCH 245/912] add gin user - photoprism (#1601) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5c14c44..1b5fb49 100644 --- a/README.md +++ b/README.md @@ -1964,3 +1964,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go. * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. +* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. From dbc330b804052d7db230729c15d041cbb775e7b6 Mon Sep 17 00:00:00 2001 From: Ismail Gjevori Date: Mon, 22 Oct 2018 17:01:14 +0200 Subject: [PATCH 246/912] Pass MaxMultipartMemory when FormFile is called (#1600) When `gin.Context.FormFile("...")` is called the `engine.MaxMultipartMemory` is never used. This PR makes sure that the `MaxMultipartMemory` is passed and removes 2 calls to `http.Request.ParseForm` since they are called from `http.Request.ParseMultipartForm` --- context.go | 7 +++++-- context_test.go | 13 +++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 809902a..c8a59bb 100644 --- a/context.go +++ b/context.go @@ -414,7 +414,6 @@ func (c *Context) PostFormArray(key string) []string { // a boolean value whether at least one value exists for the given key. func (c *Context) GetPostFormArray(key string) ([]string, bool) { req := c.Request - req.ParseForm() req.ParseMultipartForm(c.engine.MaxMultipartMemory) if values := req.PostForm[key]; len(values) > 0 { return values, true @@ -437,7 +436,6 @@ func (c *Context) PostFormMap(key string) map[string]string { // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { req := c.Request - req.ParseForm() req.ParseMultipartForm(c.engine.MaxMultipartMemory) dicts, exist := c.get(req.PostForm, key) @@ -465,6 +463,11 @@ func (c *Context) get(m map[string][]string, key string) (map[string]string, boo // FormFile returns the first file for the provided form key. func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { + if c.Request.MultipartForm == nil { + if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + return nil, err + } + } _, fh, err := c.Request.FormFile(name) return fh, err } diff --git a/context_test.go b/context_test.go index 2e972f1..fb492e0 100644 --- a/context_test.go +++ b/context_test.go @@ -84,6 +84,19 @@ func TestContextFormFile(t *testing.T) { assert.NoError(t, c.SaveUploadedFile(f, "test")) } +func TestContextFormFileFailed(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + c.engine.MaxMultipartMemory = 8 << 20 + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) +} + func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) From c65e5efc9a0ae6666819cda8e9a86db4d3d19833 Mon Sep 17 00:00:00 2001 From: Thomas Schaffer Date: Tue, 23 Oct 2018 04:56:33 +0200 Subject: [PATCH 247/912] Expose HandlerFunc in RouteInfos (#1272) --- gin.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/gin.go b/gin.go index 92c24ba..440519f 100644 --- a/gin.go +++ b/gin.go @@ -38,9 +38,10 @@ func (c HandlersChain) Last() HandlerFunc { // RouteInfo represents a request route's specification which contains method and path and its handler. type RouteInfo struct { - Method string - Path string - Handler string + Method string + Path string + Handler string + HandlerFunc HandlerFunc } // RoutesInfo defines a RouteInfo array. @@ -266,10 +267,12 @@ func (engine *Engine) Routes() (routes RoutesInfo) { func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { path += root.path if len(root.handlers) > 0 { + handlerFunc := root.handlers.Last() routes = append(routes, RouteInfo{ - Method: method, - Path: path, - Handler: nameOfFunction(root.handlers.Last()), + Method: method, + Path: path, + Handler: nameOfFunction(handlerFunc), + HandlerFunc: handlerFunc, }) } for _, child := range root.children { From 8e9619767c12607afcb086a5e9c791eda2b9e116 Mon Sep 17 00:00:00 2001 From: forging2012 Date: Wed, 31 Oct 2018 20:19:58 +0800 Subject: [PATCH 248/912] FIX r.LoadHTMLGlob("/path/to/templates") (#1616) FIX r.LoadHTMLGlob("/path/to/templates")) to r.LoadHTMLGlob("/path/to/templates") --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b5fb49..ce41332 100644 --- a/README.md +++ b/README.md @@ -1160,7 +1160,7 @@ You may use custom delims ```go r := gin.Default() r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates")) + r.LoadHTMLGlob("/path/to/templates") ``` #### Custom Template Funcs From 8fb21a8beff46febac15894200e381473889f0e6 Mon Sep 17 00:00:00 2001 From: "root@andrea:~#" Date: Thu, 1 Nov 2018 01:30:19 -0600 Subject: [PATCH 249/912] Added some comments to avoid having golint warnings (#1619) The following comments to vars, conts and method were added to pass `golinter` with 100%. ![captura de pantalla 2018-10-31 a la s 15 23 37](https://user-images.githubusercontent.com/10160626/47819725-faba3780-dd20-11e8-978c-1b3ab7de26ed.png) --- errors.go | 6 ++++-- mode.go | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/errors.go b/errors.go index 477b9d5..ab13ca6 100644 --- a/errors.go +++ b/errors.go @@ -24,9 +24,10 @@ const ( ErrorTypePrivate ErrorType = 1 << 0 // ErrorTypePublic indicates a public error. ErrorTypePublic ErrorType = 1 << 1 - // ErrorTypeAny indicates other any error. + // ErrorTypeAny indicates any other error. ErrorTypeAny ErrorType = 1<<64 - 1 - ErrorTypeNu = 2 + // ErrorTypeNu indicates any other error. + ErrorTypeNu = 2 ) // Error represents a error's specification. @@ -52,6 +53,7 @@ func (msg *Error) SetMeta(data interface{}) *Error { return msg } +// JSON creates a properly formated JSON func (msg *Error) JSON() interface{} { json := H{} if msg.Meta != nil { diff --git a/mode.go b/mode.go index 7cb0143..6a69632 100644 --- a/mode.go +++ b/mode.go @@ -28,7 +28,7 @@ const ( testCode ) -// DefaultWriter is the default io.Writer used the Gin for debug output and +// DefaultWriter is the default io.Writer used by Gin for debug output and // middleware output like Logger() or Recovery(). // Note that both Logger and Recovery provides custom ways to configure their // output io.Writer. @@ -36,6 +36,8 @@ const ( // import "github.com/mattn/go-colorable" // gin.DefaultWriter = colorable.NewColorableStdout() var DefaultWriter io.Writer = os.Stdout + +// DefaultErrorWriter is the default io.Writer used by Gin to debug errors var DefaultErrorWriter io.Writer = os.Stderr var ginMode = debugCode From 6f7fe487b38baec254343376df603adecd201f10 Mon Sep 17 00:00:00 2001 From: Barnabus Date: Thu, 1 Nov 2018 18:05:40 +1000 Subject: [PATCH 250/912] Change HTML input tags to use HTML5 syntax. (#1617) In XHTML, the tag must be properly closed, like this ``. In HTML5 the `` tag has no ending slash. https://www.w3schools.com/tags/tag_input.asp --- README.md | 8 ++++---- .../realtime-advanced/resources/room_login.templ.html | 8 ++++---- examples/realtime-chat/template.go | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ce41332..8c2c2c5 100644 --- a/README.md +++ b/README.md @@ -824,12 +824,12 @@ form.html

Check some colors

- + - + - - + +
``` diff --git a/examples/realtime-advanced/resources/room_login.templ.html b/examples/realtime-advanced/resources/room_login.templ.html index 27dac38..5fb2b33 100644 --- a/examples/realtime-advanced/resources/room_login.templ.html +++ b/examples/realtime-advanced/resources/room_login.templ.html @@ -79,19 +79,19 @@

Server-Sent Events in Go

{{.nick}}
- +
- + {{else}}
Join the SSE real-time chat
- +
- +
{{end}} diff --git a/examples/realtime-chat/template.go b/examples/realtime-chat/template.go index b9024de..4190251 100644 --- a/examples/realtime-chat/template.go +++ b/examples/realtime-chat/template.go @@ -35,9 +35,9 @@ var html = template.Must(template.New("chat_room").Parse(`

Welcome to {{.roomid}} room

- User: - Message: - + User: + Message: +
From 6be9b5437b0c1092a94800c449988699a24b3f51 Mon Sep 17 00:00:00 2001 From: Barnabus Date: Thu, 1 Nov 2018 23:48:26 +1000 Subject: [PATCH 251/912] Change HTML link tags to use HTML5 syntax. (#1621) The `` element is an empty element, it contains attributes only. In HTML5 the `` tag has no end tag. In XHTML the `` tag must be properly closed. --- examples/realtime-advanced/resources/room_login.templ.html | 2 +- examples/realtime-chat/template.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/realtime-advanced/resources/room_login.templ.html b/examples/realtime-advanced/resources/room_login.templ.html index 5fb2b33..d4991d8 100644 --- a/examples/realtime-advanced/resources/room_login.templ.html +++ b/examples/realtime-advanced/resources/room_login.templ.html @@ -20,7 +20,7 @@ - + + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(200, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} +``` + +### 定义路由日志的格式 + +路由的默认日志是: +``` +[GIN-debug] POST /foo --> main.main.func1 (3 handlers) +[GIN-debug] GET /bar --> main.main.func2 (3 handlers) +[GIN-debug] GET /status --> main.main.func3 (3 handlers) +``` + +如果要以给定格式记录此信息(例如JSON,键值或其他内容),则可以使用`gin.DebugPrintRouteFunc`定义此格式。 +在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他适合您需求的日志工具。 +```go +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) + + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) + + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + + // Listen and Server in http://0.0.0.0:8080 + r.Run() +} +``` + + +## 测试 + +`net / http / httptest`包是HTTP测试的首选方式。 + +```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") +} +``` + +测试上面的代码示例: + +```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()) +} +``` + +## 用户 + + +使用[Gin](https://github.com/gin-gonic/gin)Web框架的令人敬畏的项目列表。 + +* [drone](https://github.com/drone/drone):drone,用Go编写。 +* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务器。 +* [fnproject](https://github.com/fnproject/fn):容器本机,云无关的无服务器平台。 From 1f576fb27c3f3ec00baa0d2868e41830e37d0b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=B9=E5=AE=9D=E5=BC=BA?= Date: Fri, 23 Nov 2018 09:46:41 +0800 Subject: [PATCH 266/912] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E9=94=99=E8=AF=AF=EF=BC=8C=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E6=8A=A5=E5=BC=95=E7=94=A8=E9=94=99=E8=AF=AF=20(#1655)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复了全角括号导致超链接不能正常访问的错误。 修复了一些URL中的"/"被改成" / "的错误。 修复了一些包引用中"/"被改成" / "的错误。 修复有超链接被翻译成中文的错误。 --- README_ZH.md | 68 ++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index d411f9e..c689ba2 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -79,7 +79,7 @@ $ go get -u github.com/gin-gonic/gin import "github.com/gin-gonic/gin" ``` -3. (可选)如果使用诸如`http.StatusOK`之类的常量,则需要引入 `net / http` 包。 +3. (可选)如果使用诸如`http.StatusOK`之类的常量,则需要引入 `net/http` 包。 ```go import "net/http" @@ -200,7 +200,7 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 ## 使用 [jsoniter](https://github.com/json-iterator/go) 构建 -Gin使用`encoding / json`作为默认的json包,但您可以通过其他标签的构建更改为[jsoniter](https://github.com/json-iterator/go)。 +Gin使用`encoding/json`作为默认的json包,但您可以通过其他标签的构建更改为[jsoniter](https://github.com/json-iterator/go)。 ```sh $ go build -tags=jsoniter . @@ -359,7 +359,7 @@ ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] #### 单个文件上传 -参考问题[#774](https://github.com/gin-gonic/gin/issues/774)和详细[示例代码](examples / upload-file / single)。 +参考问题[#774](https://github.com/gin-gonic/gin/issues/774)和详细[示例代码](examples/upload-file/single)。 ```go func main() { @@ -390,7 +390,7 @@ curl -X POST http://localhost:8080/upload \ #### 多文件上传 -查看详细信息[示例代码](examples / upload-file / multiple)。 +查看详细信息[示例代码](examples/upload-file/multiple)。 ```go func main() { @@ -530,14 +530,14 @@ func main() { 要将请求主体绑定到类型中,请使用模型绑定。我们目前支持JSON,XML和标准表单值的绑定(foo = bar&boo = baz)。 -Gin使用[** go-playground / validator.v8 **](https://github.com/go-playground/validator)进行验证。检查有关标签用法的完整文档[此处](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 +Gin使用[** go-playground/validator.v8 **](https://github.com/go-playground/validator)进行验证。检查有关标签用法的完整文档[此处](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 请注意,您需要在要绑定的所有字段上设置相应的绑定标记。例如,从JSON绑定时,设置`json:“fieldname”`。 此外,Gin提供了两组绑定方法: - **类型** - 必须绑定    - **方法** - `Bind`,`BindJSON`,`BindXML`,`BindQuery` -   - **行为** - 这些方法在引擎盖下使用`MustBindWith`。如果存在绑定错误,则使用`c.AbortWithError(400,err).SetType(ErrorTypeBind)`中止请求。这将响应状态代码设置为400,并将`Content-Type`标头设置为`text / plain;字符集= UTF-8`。请注意,如果您在此之后尝试设置响应代码,则会发出警告“[GIN-debug] [警告]标题已经写入。想用422`覆盖状态代码400。如果您希望更好地控制行为,请考虑使用`ShouldBind`等效方法。 +   - **行为** - 这些方法在引擎盖下使用`MustBindWith`。如果存在绑定错误,则使用`c.AbortWithError(400,err).SetType(ErrorTypeBind)`中止请求。这将响应状态代码设置为400,并将`Content-Type`标头设置为`text/plain;字符集= UTF-8`。请注意,如果您在此之后尝试设置响应代码,则会发出警告“[GIN-debug] [警告]标题已经写入。想用422`覆盖状态代码400。如果您希望更好地控制行为,请考虑使用`ShouldBind`等效方法。 - **类型** - 应该绑定    - **方法** - `ShouldBind`,`ShouldBindJSON`,`ShouldBindXML`,`ShouldBindQuery`    - **行为** - 这些方法在引擎盖下使用`ShouldBindWith`。如果存在绑定错误,则返回错误,开发人员有责任正确处理请求和错误。 @@ -643,9 +643,9 @@ $ curl -v -X POST \ ### 自定义验证器 -也可以注册自定义验证器。 请参阅[示例代码](examples / custom-validation / server.go)。 +也可以注册自定义验证器。 请参阅[示例代码](examples/custom-validation/server.go)。 -[embedmd]:#(examples / custom-validation / server.go go) +[embedmd]:#(examples/custom-validation/server.go go) ```go package main @@ -707,12 +707,12 @@ $ 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"} ``` -[结构级验证](https://github.com/go-playground/validator/releases/tag/v8.7)也可以这种方式注册。 -请参阅[struct-lvl-validation示例](examples / struct-lvl-validations)以了解更多信息。 +[结构级验证](https://github.com/go-playground/validator/releases/tag/v8.7)也可以这种方式注册。 +请参阅[struct-lvl-validation示例](examples/struct-lvl-validations)以了解更多信息。 ### 只绑定查询字符串 -`ShouldBindQuery` 函数只绑定查询参数而不是后期数据。 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017)。 +`ShouldBindQuery` 函数只绑定查询参数而不是后期数据。 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017)。 ```go package main @@ -748,7 +748,7 @@ func startPage(c *gin.Context) { ### 绑定查询字符串或发布数据 -请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292)。 +请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292)。 ```go package main @@ -794,7 +794,7 @@ $ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03 ### 绑定 HTML 复选框 -参见[详细信息](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) +参见[详细信息](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) main.go @@ -931,7 +931,7 @@ func main() { #### SecureJSON -使用 SecureJSON 来防止 json 劫持。 如果给定的结构是数组值,则默认预 置`“while(1),”` 到响应体。 +使用 SecureJSON 来防止 json 劫持。 如果给定的结构是数组值,则默认预 置`“while(1),”` 到响应体。 ```go func main() { @@ -1067,7 +1067,7 @@ func main() { ### HTML 渲染 -使用LoadHTMLGlob()或LoadHTMLFiles() +使用LoadHTMLGlob()或LoadHTMLFiles() ```go func main() { @@ -1159,12 +1159,12 @@ func main() { ```go r := gin.Default() r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates")) + r.LoadHTMLGlob("/path/to/templates") ``` #### 自定义模板功能 -查看详细信息[示例代码](示例/模板)。 +查看详细信息[示例代码](examples/template)。 main.go @@ -1215,7 +1215,7 @@ Date: 2017/07/01 ### 多模板 -Gin 允许默认只使用一个 html 模板。 检查[多模板渲染](https://github.com/gin-contrib/multitemplate)以使用 go 1.6 `block template` 等功能。 +Gin 允许默认只使用一个 html 模板。 检查[多模板渲染](https://github.com/gin-contrib/multitemplate)以使用 go 1.6 `block template` 等功能。 ### 重定向 @@ -1355,7 +1355,7 @@ func main() { ### 自定义 HTTP 配置 -直接使用`http.ListenAndServe()`,如下所示: +直接使用`http.ListenAndServe()`,如下所示: ```go func main() { @@ -1441,7 +1441,7 @@ func main() { ### 使用 Gin 运行多个服务 -请参阅[问题](https://github.com/gin-gonic/gin/issues/346)并尝试以下示例: +请参阅[问题](https://github.com/gin-gonic/gin/issues/346)并尝试以下示例: [embedmd]:# (examples/multiple-service/main.go go) ```go @@ -1526,7 +1526,7 @@ func main() { 您想要优雅地重启或停止您的Web服务器吗? 有一些方法可以做到这一点。 -我们可以使用[fvbock / endless](https://github.com/fvbock/endless)来替换默认的`ListenAndServe`。 有关更多详细信息,请参阅问题[#296](https://github.com/gin-gonic/gin/issues/296)。 +我们可以使用[fvbock/endless](https://github.com/fvbock/endless)来替换默认的`ListenAndServe`。 有关更多详细信息,请参阅问题[#296](https://github.com/gin-gonic/gin/issues/296)。 ```go router := gin.Default() @@ -1537,11 +1537,11 @@ endless.ListenAndServe(":4242", router) 另一种替代方案: -* [manners](https://github.com/braintree/manners):礼貌的Go HTTP服务器,可以正常关闭。 -* [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 -* [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 +* [manners](https://github.com/braintree/manners):礼貌的Go HTTP服务器,可以正常关闭。 +* [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 +* [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 -如果您使用的是Go 1.8,则可能不需要使用此库! 考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./ examples / graceful-shutdown)示例。 +如果您使用的是Go 1.8,则可能不需要使用此库! 考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./examples /graceful-shutdown)示例。 [embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) ```go @@ -1637,7 +1637,7 @@ func loadTemplate() (*template.Template, error) { } ``` -请参阅`examples / assets-in-binary`目录中的完整示例。 +请参阅`examples/assets-in-binary`目录中的完整示例。 ### Bind form-data request with custom struct @@ -1788,13 +1788,13 @@ func SomeHandler(c *gin.Context) { 足以立刻调用绑定。 *只有某些格式需要此功能 - “JSON”,“XML”,“MsgPack”, `ProtoBuf`。 对于其他格式,`Query`,`Form`,`FormPost`,`FormMultipart`, -可以被`c.ShouldBind()`多次调用而不会造成任何损害 -表现(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 +可以被`c.ShouldBind()`多次调用而不会造成任何损害 +表现(见[#1341](https://github.com/gin-gonic/gin/pull/1341)。 ### http2 server 推送 -http.Pusher仅支持** go1.8 + **。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 +http.Pusher仅支持** go1.8 + **。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 [embedmd]:# (examples/http-pusher/main.go go) ```go @@ -1886,7 +1886,7 @@ func main() { ## 测试 -`net / http / httptest`包是HTTP测试的首选方式。 +`net/http/httptest`包是HTTP测试的首选方式。 ```go package main @@ -1933,8 +1933,8 @@ func TestPingRoute(t *testing.T) { ## 用户 -使用[Gin](https://github.com/gin-gonic/gin)Web框架的令人敬畏的项目列表。 +使用[Gin](https://github.com/gin-gonic/gin)Web框架的令人尊敬的项目列表。 -* [drone](https://github.com/drone/drone):drone,用Go编写。 -* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务器。 -* [fnproject](https://github.com/fnproject/fn):容器本机,云无关的无服务器平台。 +* [drone](https://github.com/drone/drone):drone,用Go编写。 +* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务器。 +* [fnproject](https://github.com/fnproject/fn):容器本机,云无关的无服务器平台。 From f52bea87f60248c00e55f7599d9584f9bc8e8978 Mon Sep 17 00:00:00 2001 From: weibaohui Date: Sat, 24 Nov 2018 19:15:19 +0800 Subject: [PATCH 267/912] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5=20(#1657)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整描述语句 --- README_ZH.md | 95 +++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index c689ba2..7867c1f 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -25,20 +25,20 @@ Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 mart - [使用 jsoniter 构建](#使用-jsoniter-构建) - [API 示例](#api-示例) - [GET,POST,PUT,PATCH,DELETE,OPTIONS 使用](#get-post-put-patch-delete-options-使用) - - [路由参数](#路由参数) - - [查询字符串参数](#查询字符串参数) + - [获取路由参数](#获取路由参数) + - [获取url查询参数](#获取url查询参数) - [Multipart Urlencoded 表单](#multipart-urlencoded-表单) - - [另一个实列 query + post form](#另一个实列:-query-+-post-form) + - [获取post表单数据(url带查询参数)](#获取post表单数据(url带查询参数)) - [映射参数 表单参数](#映射参数-表单参数) - [上传文件](#上传文件) - [路由组](#路由组) - [默认初始化 Gin](#默认初始化-gin) - - [中间件使用](#中间件使用) + - [使用中间件](#使用中间件) - [如何记录日志](#如何记录日志) - [模型绑定和验证](#模型绑定和验证) - [自定义验证器](#自定义验证器) - - [只绑定查询字符串](#只绑定查询字符串) - - [绑定查询字符串或发布数据](#绑定查询字符串或发布数据) + - [只绑定url查询参数](#只绑定url查询参数) + - [url查询参数绑定到struct(或POST表单数据)](#url查询参数绑定到struct(或POST表单数据)) - [绑定 HTML 复选框](#绑定-html-复选框) - [Multipart Urlencoded 绑定](#multipart-urlencoded-绑定) - [XML JSON YAML ProtoBuf 渲染](#xml-json-yaml-protobuf-渲染) @@ -56,8 +56,8 @@ Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 mart - [使用 Gin 运行多个服务](使用-gin-运行多个服务) - [优雅重启或停止](#优雅重启或停止) - [使用模板构建单个二进制文件](#使用模板构建单个二进制文件) - - [使用自定义结构绑定表单数据请求](#使用自定义结构绑定表单数据请求) - - [尝试将body绑定到不同的结构中](#尝试将-body-绑定到不同的结构中) + - [表单数据绑定到自定义结构体](#表单数据绑定到自定义结构体) + - [将request body绑定到不同的结构体中](#将request body绑定到不同的结构体中) - [http2 server 推送](#http2-server-推送) - [定义路由日志的格式](#定义路由日志的格式) - [测试](#测试) @@ -105,7 +105,7 @@ $ govendor init $ govendor fetch github.com/gin-gonic/gin@v1.3 ``` -4. 复制一个启动文件模板到项目目录中 +4. 复制启动文件模板到项目目录中 ```sh $ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go @@ -119,7 +119,7 @@ $ go run main.go ## 前提条件 -新版本的 Gin 需要 Go 1.6 或者更高版本并且很快就会升级到 Go 1.7. +新版本的 Gin 需要 Go 1.6 或者更高版本,并且很快就会升级到 Go 1.7. ## 快速启动 @@ -234,7 +234,7 @@ func main() { } ``` -### 路由参数 +### 获取路由参数 ```go func main() { @@ -259,7 +259,7 @@ func main() { } ``` -### 查询字符串参数 +### 获取url查询参数 ```go func main() { @@ -297,7 +297,7 @@ func main() { } ``` -### 另一个实列 query + post form +### 获取post表单数据(url带查询参数) ``` POST /post?id=1234&page=1 HTTP/1.1 @@ -380,7 +380,7 @@ func main() { } ``` -如何 `curl`: +`curl`示例: ```bash curl -X POST http://localhost:8080/upload \ @@ -414,7 +414,7 @@ func main() { } ``` -如何 `curl`: +`curl`示例: ```bash curl -X POST http://localhost:8080/upload \ @@ -465,7 +465,7 @@ r := gin.Default() ``` -### 中间件使用 +### 使用中间件 ```go func main() { // Creates a router without any middleware by default @@ -710,9 +710,9 @@ $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" [结构级验证](https://github.com/go-playground/validator/releases/tag/v8.7)也可以这种方式注册。 请参阅[struct-lvl-validation示例](examples/struct-lvl-validations)以了解更多信息。 -### 只绑定查询字符串 +### 只绑定url查询参数 -`ShouldBindQuery` 函数只绑定查询参数而不是后期数据。 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017)。 +`ShouldBindQuery` 函数只绑定url查询参数而不是post字段。 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017)。 ```go package main @@ -746,7 +746,7 @@ func startPage(c *gin.Context) { ``` -### 绑定查询字符串或发布数据 +### url查询参数绑定到struct(或POST表单数据) 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292)。 @@ -872,7 +872,7 @@ func main() { } ``` -Test it with: +测试: ```sh $ curl -v --form user=user --form password=password http://localhost:8080/login ``` @@ -999,8 +999,8 @@ func main() { #### PureJSON -通常,JSON 用其 unicod e实体替换特殊 HTML 字符,例如 `<` 变为 `\ u003c`。 如果要按字面意思对这些字符进行编码,则可以使用 PureJSON。 -Go 1.6 及更低版本无法使用此功能。 +通常,JSON使用unicode替换特殊HTML字符,例如 `<` 变为 `\ u003c`。 如果要按字面意思对这些字符进行编码,则可以使用 PureJSON。 +Go 1.6及更低版本无法使用此功能。 ```go func main() { @@ -1382,7 +1382,7 @@ func main() { ### Let's Encrypt 支持 -单行 LetsEncrypt HTTPS 服务器的示例。 +一行代码支持 LetsEncrypt HTTPS示例。 [embedmd]:# (examples/auto-tls/example1/main.go go) ```go @@ -1407,7 +1407,7 @@ func main() { } ``` -自定义autocert管理器的示例。 +autocert使用示例。 [embedmd]:# (examples/auto-tls/example2/main.go go) ```go @@ -1535,13 +1535,13 @@ router.GET("/", handler) endless.ListenAndServe(":4242", router) ``` -另一种替代方案: +替代方案: * [manners](https://github.com/braintree/manners):礼貌的Go HTTP服务器,可以正常关闭。 * [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 * [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 -如果您使用的是Go 1.8,则可能不需要使用此库! 考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./examples /graceful-shutdown)示例。 +如果您使用的是Go 1.8,可以考虑使用http.Server内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./examples /graceful-shutdown)示例。 [embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) ```go @@ -1597,7 +1597,7 @@ func main() { ### 使用模板构建单个二进制文件 -您可以使用[go-assets] []将服务器构建到包含模板的单个二进制文件中。 +您可以使用[go-assets]将服务器打包为包含模板的单个二进制可执行文件。 [go-assets]:https://github.com/jessevdk/go-assets @@ -1639,7 +1639,7 @@ func loadTemplate() (*template.Template, error) { 请参阅`examples/assets-in-binary`目录中的完整示例。 -### Bind form-data request with custom struct +### 表单数据绑定到自定义结构体 以下示例使用自定义结构: @@ -1731,10 +1731,9 @@ type StructZ struct { 总之,只支持现在没有`form`的嵌套自定义结构。 -### 尝试将 body 绑定到不同的结构中 +### 将request body绑定到不同的结构体中 -绑定请求体的常规方法使用`c.Request.Body`和它们 -不能多次调用。 +一般通过调用`c.Request.Body`方法绑定数据,但不能多次调用这个方法。 ```go type formA struct { @@ -1760,7 +1759,7 @@ func SomeHandler(c *gin.Context) { } ``` -为此,您可以使用`c.ShouldBindBodyWith`. +为此,要想多次绑定,需要使用`c.ShouldBindBodyWith`. ```go func SomeHandler(c *gin.Context) { @@ -1782,14 +1781,11 @@ func SomeHandler(c *gin.Context) { ``` -482/5000 -*`c.ShouldBindBodyWith`在绑定之前将body存储到上下文中。 这有 -对性能有轻微影响,所以如果你这样做,你不应该使用这种方法 -足以立刻调用绑定。 -*只有某些格式需要此功能 - “JSON”,“XML”,“MsgPack”, -`ProtoBuf`。 对于其他格式,`Query`,`Form`,`FormPost`,`FormMultipart`, -可以被`c.ShouldBind()`多次调用而不会造成任何损害 -表现(见[#1341](https://github.com/gin-gonic/gin/pull/1341)。 +*`c.ShouldBindBodyWith`会在绑定之前将body存储到上下文中。 这会 +对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 +*只有某些格式需要此功能 ,如“JSON”,“XML”,“MsgPack”, +`ProtoBuf`。 对于其他格式,如`Query`,`Form`,`FormPost`,`FormMultipart`, +可以多次调用`c.ShouldBind()`而不会造成任任何性能损失(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 ### http2 server 推送 @@ -1843,15 +1839,15 @@ func main() { ### 定义路由日志的格式 -路由的默认日志是: +默认的路由日志格式: ``` [GIN-debug] POST /foo --> main.main.func1 (3 handlers) [GIN-debug] GET /bar --> main.main.func2 (3 handlers) [GIN-debug] GET /status --> main.main.func3 (3 handlers) ``` -如果要以给定格式记录此信息(例如JSON,键值或其他内容),则可以使用`gin.DebugPrintRouteFunc`定义此格式。 -在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他适合您需求的日志工具。 +如果要以指的格式(例如JSON,Key Values或其他格式)记录信息,则可以使用`gin.DebugPrintRouteFunc`指定格式。 +在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他满足需求的日志工具。 ```go import ( "log" @@ -1886,7 +1882,7 @@ func main() { ## 测试 -`net/http/httptest`包是HTTP测试的首选方式。 +HTTP测试首选`net/http/httptest`包。 ```go package main @@ -1905,7 +1901,7 @@ func main() { } ``` -测试上面的代码示例: +上面这段代码的测试用例: ```go package main @@ -1933,8 +1929,9 @@ func TestPingRoute(t *testing.T) { ## 用户 -使用[Gin](https://github.com/gin-gonic/gin)Web框架的令人尊敬的项目列表。 +使用[Gin](https://github.com/gin-gonic/gin)框架的著名项目。 -* [drone](https://github.com/drone/drone):drone,用Go编写。 -* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务器。 -* [fnproject](https://github.com/fnproject/fn):容器本机,云无关的无服务器平台。 +* [drone](https://github.com/drone/drone):用Go编写的基于docker的持续集成平台。 +* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务。 +* [fnproject](https://github.com/fnproject/fn):容器驱动、云无关的无服务器平台。 +* [photoprism](https://github.com/photoprism/photoprism): 用Go编写的基于TensorFlow的个人相册管理系统. From 331af2219c9e6376e39221c6e85255fbfc64dcc8 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sat, 24 Nov 2018 20:49:26 +0800 Subject: [PATCH 268/912] add krakend to gin user list (#1658) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3f0c917..0f74d30 100644 --- a/README.md +++ b/README.md @@ -2000,3 +2000,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. +* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. From 687d8b9ac6178c31b0b1119cc669e6062a59bc93 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 25 Nov 2018 20:52:46 +0800 Subject: [PATCH 269/912] add picfit to gin user list (#1661) agreed with the project's author. cc @thoas thanks! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0f74d30..e7b92b2 100644 --- a/README.md +++ b/README.md @@ -2001,3 +2001,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. * [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. +* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. From 465ead47d0f40a24b067798f5bab1c263f09660d Mon Sep 17 00:00:00 2001 From: weibaohui Date: Sun, 25 Nov 2018 21:18:00 +0800 Subject: [PATCH 270/912] doc: update README_ZH.md (#1659) --- README_ZH.md | 166 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 111 insertions(+), 55 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 7867c1f..8c9f8ab 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -19,16 +19,16 @@ Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 mart - [安装](#安装) - [前提条件](#前提条件) -- [快速启动](#快速启动) +- [快速开始](#快速开始) - [性能测试](#性能测试) - [Gin v1 稳定版](#gin-v1-稳定版) -- [使用 jsoniter 构建](#使用-jsoniter-构建) +- [使用 jsoniter ](#使用-jsoniter) - [API 示例](#api-示例) - [GET,POST,PUT,PATCH,DELETE,OPTIONS 使用](#get-post-put-patch-delete-options-使用) - [获取路由参数](#获取路由参数) - [获取url查询参数](#获取url查询参数) - [Multipart Urlencoded 表单](#multipart-urlencoded-表单) - - [获取post表单数据(url带查询参数)](#获取post表单数据(url带查询参数)) + - [获取post表单数据(url带查询参数)](#获取post表单数据url带查询参数) - [映射参数 表单参数](#映射参数-表单参数) - [上传文件](#上传文件) - [路由组](#路由组) @@ -38,28 +38,30 @@ Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 mart - [模型绑定和验证](#模型绑定和验证) - [自定义验证器](#自定义验证器) - [只绑定url查询参数](#只绑定url查询参数) - - [url查询参数绑定到struct(或POST表单数据)](#url查询参数绑定到struct(或POST表单数据)) + - [url查询参数或表单数据绑定到结构体](#url查询参数或表单数据绑定到结构体) + - [url路径参数绑定](#url路径参数绑定) - [绑定 HTML 复选框](#绑定-html-复选框) - [Multipart Urlencoded 绑定](#multipart-urlencoded-绑定) - [XML JSON YAML ProtoBuf 渲染](#xml-json-yaml-protobuf-渲染) - [SecureJSON](#SecureJSON) - [静态文件服务](#静态文件服务) - - [从读者服务数据](#从读者服务数据) + - [从reader 读取数据](#从-reader-读取数据) - [HTML 渲染](#html-渲染) - [多模板](#多模板) - [重定向](#重定向) - [自定义中间件](#自定义中间件) - [使用 BasicAuth() 中间件](#使用-basicauth()-中间件) - - [Goroutines](#goroutines) + - [在中间件中使用Goroutines](#在中间件中使用Goroutines) - [自定义 HTTP 配置](#自定义-http-配置) - [Let's Encrypt 支持](#lets-encrypt-支持) - [使用 Gin 运行多个服务](使用-gin-运行多个服务) - [优雅重启或停止](#优雅重启或停止) - - [使用模板构建单个二进制文件](#使用模板构建单个二进制文件) + - [静态资源嵌入](#静态资源嵌入) - [表单数据绑定到自定义结构体](#表单数据绑定到自定义结构体) - - [将request body绑定到不同的结构体中](#将request body绑定到不同的结构体中) + - [将request body绑定到不同的结构体中](#将request-body绑定到不同的结构体中) - [http2 server 推送](#http2-server-推送) - [定义路由日志的格式](#定义路由日志的格式) + - [如何使用Cookie](#如何使用Cookie) - [测试](#测试) - [用户](#用户) @@ -119,9 +121,9 @@ $ go run main.go ## 前提条件 -新版本的 Gin 需要 Go 1.6 或者更高版本,并且很快就会升级到 Go 1.7. +新版本的 Gin 需要 Go 1.6 或者更高版本,并且很快就会要求升级到 Go 1.7. -## 快速启动 +## 快速开始 ```sh # assume the following codes in example.go file @@ -192,15 +194,15 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 ## Gin v1 稳定版 -- [x] 零分配路由器。 +- [x] 零分配路由。 - [x] 仍然是最快的http路由器和框架。 -- [x] 完整的单元测试套件 +- [x] 完整的单元测试支持 - [x] 对战测试 -- [x] API冻结,新版本不会破坏您的代码。 +- [x] API冻结,使用新版本不需要修改原有代码。 -## 使用 [jsoniter](https://github.com/json-iterator/go) 构建 +## 使用 [jsoniter](https://github.com/json-iterator/go) -Gin使用`encoding/json`作为默认的json包,但您可以通过其他标签的构建更改为[jsoniter](https://github.com/json-iterator/go)。 +Gin默认使用`encoding/json`解析json数据,但您可以通过`go build -tags=`更改为使用[jsoniter](https://github.com/json-iterator/go)。 ```sh $ go build -tags=jsoniter . @@ -208,7 +210,7 @@ $ go build -tags=jsoniter . ## API 示例 -### GET, POST, PUT, PATCH, DELETE , OPTIONS 使用 +### 使用GET, POST, PUT, PATCH, DELETE , OPTIONS ```go func main() { @@ -297,7 +299,7 @@ func main() { } ``` -### 获取post表单数据(url带查询参数) +### 获取post表单数据(url带查询参数) ``` POST /post?id=1234&page=1 HTTP/1.1 @@ -528,23 +530,23 @@ func main() { ### 模型绑定和验证 -要将请求主体绑定到类型中,请使用模型绑定。我们目前支持JSON,XML和标准表单值的绑定(foo = bar&boo = baz)。 +要将请求主体绑定到结构体中,请使用模型绑定。Gin目前支持JSON、XML、YAML和标准表单值的绑定(foo=bar&boo=baz)。 -Gin使用[** go-playground/validator.v8 **](https://github.com/go-playground/validator)进行验证。检查有关标签用法的完整文档[此处](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 +Gin使用[go-playground/validator.v8](https://github.com/go-playground/validator)进行验证。[完整文档](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 -请注意,您需要在要绑定的所有字段上设置相应的绑定标记。例如,从JSON绑定时,设置`json:“fieldname”`。 +使用时,需要在要绑定的所有字段上,设置相应的tag。例如,使用JSON绑定时,字段tag设置为`json:"fieldname"`。 -此外,Gin提供了两组绑定方法: -- **类型** - 必须绑定 -   - **方法** - `Bind`,`BindJSON`,`BindXML`,`BindQuery` -   - **行为** - 这些方法在引擎盖下使用`MustBindWith`。如果存在绑定错误,则使用`c.AbortWithError(400,err).SetType(ErrorTypeBind)`中止请求。这将响应状态代码设置为400,并将`Content-Type`标头设置为`text/plain;字符集= UTF-8`。请注意,如果您在此之后尝试设置响应代码,则会发出警告“[GIN-debug] [警告]标题已经写入。想用422`覆盖状态代码400。如果您希望更好地控制行为,请考虑使用`ShouldBind`等效方法。 -- **类型** - 应该绑定 +Gin提供了两类绑定方法: +- **MustBind** - + - **方法** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` + - **说明** - 这些方法属于`MustBindWith`的具体调用。如果发生绑定错误,则请求终止,并触发`c.AbortWithError(400,err).SetType(ErrorTypeBind)`。响应状态码被设置为400,`Content-Type`被设置为`text/plain;charset= UTF-8`。如果您在此之后尝试设置响应状态码,Gin会输出日志“ `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`。如果您希望更好地把控绑定,请考虑使用`ShouldBind`等效方法。 +- **ShouldBind** -    - **方法** - `ShouldBind`,`ShouldBindJSON`,`ShouldBindXML`,`ShouldBindQuery` -   - **行为** - 这些方法在引擎盖下使用`ShouldBindWith`。如果存在绑定错误,则返回错误,开发人员有责任正确处理请求和错误。 + - **说明** - 这些方法属于`ShouldBindWith`的具体调用。如果发生绑定错误,Gin会返回错误。由您处理错误以及请求。 -使用Bind方法时,Gin会尝试根据Content-Type标头推断出绑定器。如果你确定你绑定了什么,你可以使用 `MustBindWith` 或 `ShouldBindWith`。 +使用Bind方法时,Gin会根据Content-Type尝试推断如何绑定,如果您明确知道,您可以使用 `MustBindWith` 或 `ShouldBindWith`。 -您还可以指定需要特定字段。如果字段用 `binding:“必需”` 来装饰,并且在绑定时具有空值,则会返回错误。 +指定必须绑定的字段,在该字段Tag上加上`binding:"required"` ,如果绑定时是空值,Gin会报错。 ```go // Binding from JSON @@ -615,7 +617,7 @@ func main() { } ``` -**简单请求** +**测试** ```shell $ curl -v -X POST \ http://localhost:8080/loginJSON \ @@ -637,15 +639,14 @@ $ curl -v -X POST \ {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} ``` -**跳过验证** +**忽略验证** -使用上面 的`curl` 命令运行上面的例子时,它返回错误。 因为这个例子使用 `binding:'需要``````。 如果使用 `binding:“ - ``````,那么在再次运行上面的例子时它不会返回错误。 +使用`curl` 命令运行上面的例子时,它返回错误,因为这个例子使用`binding:"required"` 。 如果使用 `binding:"-"` ,那么再次运行上面的例子时它不会返回错误。 ### 自定义验证器 -也可以注册自定义验证器。 请参阅[示例代码](examples/custom-validation/server.go)。 +注册自定义验证器, 请参阅[示例代码](examples/custom-validation/server.go)。 -[embedmd]:#(examples/custom-validation/server.go go) ```go package main @@ -707,7 +708,7 @@ $ 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"} ``` -[结构级验证](https://github.com/go-playground/validator/releases/tag/v8.7)也可以这种方式注册。 +结构体验证也可以参考[这种方法](https://github.com/go-playground/validator/releases/tag/v8.7)注册。 请参阅[struct-lvl-validation示例](examples/struct-lvl-validations)以了解更多信息。 ### 只绑定url查询参数 @@ -746,7 +747,7 @@ func startPage(c *gin.Context) { ``` -### url查询参数绑定到struct(或POST表单数据) +### url查询参数或表单数据绑定到结构体 请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292)。 @@ -791,6 +792,39 @@ func startPage(c *gin.Context) { ```sh $ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" ``` +### url路径参数绑定 + +查看[详细信息](https://github.com/gin-gonic/gin/issues/846). + +```go +package main + +import "github.com/gin-gonic/gin" + +type Person struct { + ID string `uri:"id" binding:"required,uuid"` + Name string `uri:"name" binding:"required"` +} + +func main() { + route := gin.Default() + route.GET("/:name/:id", func(c *gin.Context) { + var person Person + if err := c.ShouldBindUri(&person); err != nil { + c.JSON(400, gin.H{"msg": err}) + return + } + c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) + }) + route.Run(":8088") +} +``` + +测试: +```sh +$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 +$ curl -v localhost:8088/thinkerou/not-uuid +``` ### 绑定 HTML 复选框 @@ -1320,9 +1354,9 @@ func main() { } ``` -### Goroutines +### 在中间件中使用Goroutines -当在中间件或处理程序中启动新的 Goroutines 时,你不应该**使用其中的原始上下文,你必须使用只读副本。 +当在中间件或handler中启动新的Goroutines时,不能使用原始上下文,必须使用只读副本。 ```go func main() { @@ -1541,7 +1575,8 @@ endless.ListenAndServe(":4242", router) * [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 * [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 -如果您使用的是Go 1.8,可以考虑使用http.Server内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 请参阅gin的完整[graceful-shutdown](./examples /graceful-shutdown)示例。 +如果您使用的是Go 1.8,可以考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 +请参阅gin的完整[graceful-shutdown](/examples /graceful-shutdown)示例。 [embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) ```go @@ -1595,11 +1630,9 @@ func main() { } ``` -### 使用模板构建单个二进制文件 +### 静态资源嵌入 -您可以使用[go-assets]将服务器打包为包含模板的单个二进制可执行文件。 - -[go-assets]:https://github.com/jessevdk/go-assets +使用[go-assets](https://github.com/jessevdk/go-assets)将静态资源打包到可执行文件中。 ```go func main() { @@ -1641,7 +1674,7 @@ func loadTemplate() (*template.Template, error) { ### 表单数据绑定到自定义结构体 -以下示例使用自定义结构: +以下示例使用自定义结构体: ```go type StructA struct { @@ -1702,7 +1735,7 @@ func main() { } ``` -使用命令`curl`命令结果: +`curl`命令示例: ``` $ curl "http://localhost:8080/getb?field_a=hello&field_b=world" @@ -1713,7 +1746,7 @@ $ curl "http://localhost:8080/getd?field_x=hello&field_d=world" {"d":"world","x":{"FieldX":"hello"}} ``` -**注意**:不支持以下样式结构: +**注意**:不支持以下格式结构体: ```go type StructX struct { @@ -1729,7 +1762,7 @@ type StructZ struct { } ``` -总之,只支持现在没有`form`的嵌套自定义结构。 +一句话,现在只支持没有`form`的嵌套结构体。 ### 将request body绑定到不同的结构体中 @@ -1781,16 +1814,13 @@ func SomeHandler(c *gin.Context) { ``` -*`c.ShouldBindBodyWith`会在绑定之前将body存储到上下文中。 这会 -对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 -*只有某些格式需要此功能 ,如“JSON”,“XML”,“MsgPack”, -`ProtoBuf`。 对于其他格式,如`Query`,`Form`,`FormPost`,`FormMultipart`, -可以多次调用`c.ShouldBind()`而不会造成任任何性能损失(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 +* `c.ShouldBindBodyWith`会在绑定之前将body存储到上下文中。 这会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 +* 只有某些格式需要此功能 ,如`JSON`、`XML`、`MsgPack`、`ProtoBuf`。 对于其他格式,如`Query`、`Form`、`FormPost`、`FormMultipart`可以多次调用`c.ShouldBind()`而不会造成任任何性能损失(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 ### http2 server 推送 -http.Pusher仅支持** go1.8 + **。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 +http.Pusher仅支持go1.8 +。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 [embedmd]:# (examples/http-pusher/main.go go) ```go @@ -1878,7 +1908,34 @@ func main() { r.Run() } ``` +### 如何使用Cookie + +```go +import ( + "fmt" + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { + + cookie, err := c.Cookie("gin_cookie") + + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } + + fmt.Printf("Cookie value: %s \n", cookie) + }) + + router.Run() +} +``` ## 测试 @@ -1927,11 +1984,10 @@ func TestPingRoute(t *testing.T) { ``` ## 用户 - - -使用[Gin](https://github.com/gin-gonic/gin)框架的著名项目。 +使用[Gin](https://github.com/gin-gonic/gin)框架的著名项目: * [drone](https://github.com/drone/drone):用Go编写的基于docker的持续集成平台。 * [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务。 * [fnproject](https://github.com/fnproject/fn):容器驱动、云无关的无服务器平台。 * [photoprism](https://github.com/photoprism/photoprism): 用Go编写的基于TensorFlow的个人相册管理系统. +* [krakend](https://github.com/devopsfaith/krakend): 表现优异的API网关中间件。 From 149ef75cdd1848d76b0bc2a205898a5eb53cb059 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 26 Nov 2018 21:05:54 +0800 Subject: [PATCH 271/912] doc: remove README_ZH.md (#1667) --- README_ZH.md | 1993 -------------------------------------------------- 1 file changed, 1993 deletions(-) delete mode 100644 README_ZH.md diff --git a/README_ZH.md b/README_ZH.md deleted file mode 100644 index 8c9f8ab..0000000 --- a/README_ZH.md +++ /dev/null @@ -1,1993 +0,0 @@ -# Gin Web 框架中文文档 - - - -[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) -[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) -[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) -[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) -[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) -[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) -[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) - -Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 martini-like API 框架, 比 [httprouter](https://github.com/julienschmidt/httprouter) 的速度快了40倍. 如果你是性能和高效的追求者, 那么你会爱上 Gin. - -![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) - -## Contents - -- [安装](#安装) -- [前提条件](#前提条件) -- [快速开始](#快速开始) -- [性能测试](#性能测试) -- [Gin v1 稳定版](#gin-v1-稳定版) -- [使用 jsoniter ](#使用-jsoniter) -- [API 示例](#api-示例) - - [GET,POST,PUT,PATCH,DELETE,OPTIONS 使用](#get-post-put-patch-delete-options-使用) - - [获取路由参数](#获取路由参数) - - [获取url查询参数](#获取url查询参数) - - [Multipart Urlencoded 表单](#multipart-urlencoded-表单) - - [获取post表单数据(url带查询参数)](#获取post表单数据url带查询参数) - - [映射参数 表单参数](#映射参数-表单参数) - - [上传文件](#上传文件) - - [路由组](#路由组) - - [默认初始化 Gin](#默认初始化-gin) - - [使用中间件](#使用中间件) - - [如何记录日志](#如何记录日志) - - [模型绑定和验证](#模型绑定和验证) - - [自定义验证器](#自定义验证器) - - [只绑定url查询参数](#只绑定url查询参数) - - [url查询参数或表单数据绑定到结构体](#url查询参数或表单数据绑定到结构体) - - [url路径参数绑定](#url路径参数绑定) - - [绑定 HTML 复选框](#绑定-html-复选框) - - [Multipart Urlencoded 绑定](#multipart-urlencoded-绑定) - - [XML JSON YAML ProtoBuf 渲染](#xml-json-yaml-protobuf-渲染) - - [SecureJSON](#SecureJSON) - - [静态文件服务](#静态文件服务) - - [从reader 读取数据](#从-reader-读取数据) - - [HTML 渲染](#html-渲染) - - [多模板](#多模板) - - [重定向](#重定向) - - [自定义中间件](#自定义中间件) - - [使用 BasicAuth() 中间件](#使用-basicauth()-中间件) - - [在中间件中使用Goroutines](#在中间件中使用Goroutines) - - [自定义 HTTP 配置](#自定义-http-配置) - - [Let's Encrypt 支持](#lets-encrypt-支持) - - [使用 Gin 运行多个服务](使用-gin-运行多个服务) - - [优雅重启或停止](#优雅重启或停止) - - [静态资源嵌入](#静态资源嵌入) - - [表单数据绑定到自定义结构体](#表单数据绑定到自定义结构体) - - [将request body绑定到不同的结构体中](#将request-body绑定到不同的结构体中) - - [http2 server 推送](#http2-server-推送) - - [定义路由日志的格式](#定义路由日志的格式) - - [如何使用Cookie](#如何使用Cookie) -- [测试](#测试) -- [用户](#用户) - -## 安装 - -要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区。 - -1. 下载并安装 gin: - -```sh -$ go get -u github.com/gin-gonic/gin -``` - -2. 将 gin 引入到代码中: - -```go -import "github.com/gin-gonic/gin" -``` - -3. (可选)如果使用诸如`http.StatusOK`之类的常量,则需要引入 `net/http` 包。 - -```go -import "net/http" -``` - -### 使用 [Govendor](https://github.com/kardianos/govendor) 工具创建项目 - -1. `go get` govendor - -```sh -$ go get github.com/kardianos/govendor -``` -2.创建项目并且 `cd` 到项目目录中 - -```sh -$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" -``` - -3. 使用 govendor 初始化项目,并且引入gin - -```sh -$ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.3 -``` - -4. 复制启动文件模板到项目目录中 - -```sh -$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go -``` - -5.启动项目 - -```sh -$ go run main.go -``` - -## 前提条件 - -新版本的 Gin 需要 Go 1.6 或者更高版本,并且很快就会要求升级到 Go 1.7. - -## 快速开始 - -```sh -# assume the following codes in example.go file -$ cat example.go -``` - -```go -package main - -import "github.com/gin-gonic/gin" - -func main() { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.JSON(200, gin.H{ - "message": "pong", - }) - }) - r.Run() // listen and serve on 0.0.0.0:8080 -} -``` - -``` -# run example.go and visit 0.0.0.0:8080/ping on browser -$ go run example.go -``` - -## 性能测试 - -Gin 使用自定义版本的 [HttpRouter](https://github.com/julienschmidt/httprouter) - -[所有性能测试](/BENCHMARKS.md) - -Benchmark name | (1) | (2) | (3) | (4) ---------------------------------------------|-----------:|------------:|-----------:|---------: -**BenchmarkGin_GithubAll** | **30000** | **48375** | **0** | **0** -BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167 -BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943 -BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812 -BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453 -BenchmarkDenco_GithubAll | 10000 | 158819 | 20224 | 167 -BenchmarkEcho_GithubAll | 10000 | 154700 | 6496 | 203 -BenchmarkGocraftWeb_GithubAll | 3000 | 570806 | 131656 | 1686 -BenchmarkGoji_GithubAll | 2000 | 818034 | 56112 | 334 -BenchmarkGojiv2_GithubAll | 2000 | 1213973 | 274768 | 3712 -BenchmarkGoJsonRest_GithubAll | 2000 | 785796 | 134371 | 2737 -BenchmarkGoRestful_GithubAll | 300 | 5238188 | 689672 | 4519 -BenchmarkGorillaMux_GithubAll | 100 | 10257726 | 211840 | 2272 -BenchmarkHttpRouter_GithubAll | 20000 | 105414 | 13792 | 167 -BenchmarkHttpTreeMux_GithubAll | 10000 | 319934 | 65856 | 671 -BenchmarkKocha_GithubAll | 10000 | 209442 | 23304 | 843 -BenchmarkLARS_GithubAll | 20000 | 62565 | 0 | 0 -BenchmarkMacaron_GithubAll | 2000 | 1161270 | 204194 | 2000 -BenchmarkMartini_GithubAll | 200 | 9991713 | 226549 | 2325 -BenchmarkPat_GithubAll | 200 | 5590793 | 1499568 | 27435 -BenchmarkPossum_GithubAll | 10000 | 319768 | 84448 | 609 -BenchmarkR2router_GithubAll | 10000 | 305134 | 77328 | 979 -BenchmarkRivet_GithubAll | 10000 | 132134 | 16272 | 167 -BenchmarkTango_GithubAll | 3000 | 552754 | 63826 | 1618 -BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 | 5374 -BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848 -BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609 - -- (1): 在不断的时间内实现总重复,更高意味着更自信的结果 -- (2): 单次重复持续时间(ns / op),越低越好 -- (3): 堆内存(B / op),越低越好 -- (4): 每次重复的平均分配(allocs / op)越低越好 - -## Gin v1 稳定版 - -- [x] 零分配路由。 -- [x] 仍然是最快的http路由器和框架。 -- [x] 完整的单元测试支持 -- [x] 对战测试 -- [x] API冻结,使用新版本不需要修改原有代码。 - -## 使用 [jsoniter](https://github.com/json-iterator/go) - -Gin默认使用`encoding/json`解析json数据,但您可以通过`go build -tags=`更改为使用[jsoniter](https://github.com/json-iterator/go)。 - -```sh -$ go build -tags=jsoniter . -``` - -## API 示例 - -### 使用GET, POST, PUT, PATCH, DELETE , OPTIONS - -```go -func main() { - // Disable Console Color - // gin.DisableConsoleColor() - - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/someGet", getting) - router.POST("/somePost", posting) - router.PUT("/somePut", putting) - router.DELETE("/someDelete", deleting) - router.PATCH("/somePatch", patching) - router.HEAD("/someHead", head) - router.OPTIONS("/someOptions", options) - - // By default it serves on :8080 unless a - // PORT environment variable was defined. - router.Run() - // router.Run(":3000") for a hard coded port -} -``` - -### 获取路由参数 - -```go -func main() { - router := gin.Default() - - // This handler will match /user/john but will not match /user/ or /user - router.GET("/user/:name", func(c *gin.Context) { - name := c.Param("name") - c.String(http.StatusOK, "Hello %s", name) - }) - - // However, this one will match /user/john/ and also /user/john/send - // If no other routers match /user/john, it will redirect to /user/john/ - router.GET("/user/:name/*action", func(c *gin.Context) { - name := c.Param("name") - action := c.Param("action") - message := name + " is " + action - c.String(http.StatusOK, message) - }) - - router.Run(":8080") -} -``` - -### 获取url查询参数 - -```go -func main() { - router := gin.Default() - - // Query string parameters are parsed using the existing underlying request object. - // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe - router.GET("/welcome", func(c *gin.Context) { - firstname := c.DefaultQuery("firstname", "Guest") - lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") - - c.String(http.StatusOK, "Hello %s %s", firstname, lastname) - }) - router.Run(":8080") -} -``` - -### Multipart Urlencoded 表单 - -```go -func main() { - router := gin.Default() - - router.POST("/form_post", func(c *gin.Context) { - message := c.PostForm("message") - nick := c.DefaultPostForm("nick", "anonymous") - - c.JSON(200, gin.H{ - "status": "posted", - "message": message, - "nick": nick, - }) - }) - router.Run(":8080") -} -``` - -### 获取post表单数据(url带查询参数) - -``` -POST /post?id=1234&page=1 HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -name=manu&message=this_is_great -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - id := c.Query("id") - page := c.DefaultQuery("page", "0") - name := c.PostForm("name") - message := c.PostForm("message") - - fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) - }) - router.Run(":8080") -} -``` - -``` -id: 1234; page: 1; name: manu; message: this_is_great -``` - -### 映射参数 表单参数 - -``` -POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -names[first]=thinkerou&names[second]=tianou -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - ids := c.QueryMap("ids") - names := c.PostFormMap("names") - - fmt.Printf("ids: %v; names: %v", ids, names) - }) - router.Run(":8080") -} -``` - -``` -ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] -``` - -### 上传文件 - -#### 单个文件上传 - -参考问题[#774](https://github.com/gin-gonic/gin/issues/774)和详细[示例代码](examples/upload-file/single)。 - -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - // router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // single file - file, _ := c.FormFile("file") - log.Println(file.Filename) - - // Upload the file to specific dst. - // c.SaveUploadedFile(file, dst) - - c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) - }) - router.Run(":8080") -} -``` - -`curl`示例: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "file=@/Users/appleboy/test.zip" \ - -H "Content-Type: multipart/form-data" -``` - -#### 多文件上传 - -查看详细信息[示例代码](examples/upload-file/multiple)。 - -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - // router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // Multipart form - form, _ := c.MultipartForm() - files := form.File["upload[]"] - - for _, file := range files { - log.Println(file.Filename) - - // Upload the file to specific dst. - // c.SaveUploadedFile(file, dst) - } - c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) - }) - router.Run(":8080") -} -``` - -`curl`示例: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "upload[]=@/Users/appleboy/test1.zip" \ - -F "upload[]=@/Users/appleboy/test2.zip" \ - -H "Content-Type: multipart/form-data" -``` - -### 路由组 - -```go -func main() { - router := gin.Default() - - // Simple group: v1 - v1 := router.Group("/v1") - { - v1.POST("/login", loginEndpoint) - v1.POST("/submit", submitEndpoint) - v1.POST("/read", readEndpoint) - } - - // Simple group: v2 - v2 := router.Group("/v2") - { - v2.POST("/login", loginEndpoint) - v2.POST("/submit", submitEndpoint) - v2.POST("/read", readEndpoint) - } - - router.Run(":8080") -} -``` - -### 默认初始化 Gin - -用 - -```go -r := gin.New() -``` - -代替 - -```go -// Default With the Logger and Recovery middleware already attached -r := gin.Default() -``` - - -### 使用中间件 -```go -func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. - // By default gin.DefaultWriter = os.Stdout - r.Use(gin.Logger()) - - // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.Recovery()) - - // Per route middleware, you can add as many as you desire. - r.GET("/benchmark", MyBenchLogger(), benchEndpoint) - - // Authorization group - // authorized := r.Group("/", AuthRequired()) - // exactly the same as: - authorized := r.Group("/") - // per group middleware! in this case we use the custom created - // AuthRequired() middleware just in the "authorized" group. - authorized.Use(AuthRequired()) - { - authorized.POST("/login", loginEndpoint) - authorized.POST("/submit", submitEndpoint) - authorized.POST("/read", readEndpoint) - - // nested group - testing := authorized.Group("testing") - testing.GET("/analytics", analyticsEndpoint) - } - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### 如何记录日志 -```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") -} -``` - -### 模型绑定和验证 - -要将请求主体绑定到结构体中,请使用模型绑定。Gin目前支持JSON、XML、YAML和标准表单值的绑定(foo=bar&boo=baz)。 - -Gin使用[go-playground/validator.v8](https://github.com/go-playground/validator)进行验证。[完整文档](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。 - -使用时,需要在要绑定的所有字段上,设置相应的tag。例如,使用JSON绑定时,字段tag设置为`json:"fieldname"`。 - -Gin提供了两类绑定方法: -- **MustBind** - - - **方法** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` - - **说明** - 这些方法属于`MustBindWith`的具体调用。如果发生绑定错误,则请求终止,并触发`c.AbortWithError(400,err).SetType(ErrorTypeBind)`。响应状态码被设置为400,`Content-Type`被设置为`text/plain;charset= UTF-8`。如果您在此之后尝试设置响应状态码,Gin会输出日志“ `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`。如果您希望更好地把控绑定,请考虑使用`ShouldBind`等效方法。 -- **ShouldBind** - -   - **方法** - `ShouldBind`,`ShouldBindJSON`,`ShouldBindXML`,`ShouldBindQuery` - - **说明** - 这些方法属于`ShouldBindWith`的具体调用。如果发生绑定错误,Gin会返回错误。由您处理错误以及请求。 - -使用Bind方法时,Gin会根据Content-Type尝试推断如何绑定,如果您明确知道,您可以使用 `MustBindWith` 或 `ShouldBindWith`。 - -指定必须绑定的字段,在该字段Tag上加上`binding:"required"` ,如果绑定时是空值,Gin会报错。 - -```go -// Binding from JSON -type Login struct { - User string `form:"user" json:"user" xml:"user" binding:"required"` - Password string `form:"password" json:"password" xml:"password" binding:"required"` -} - -func main() { - router := gin.Default() - - // Example for binding JSON ({"user": "manu", "password": "123"}) - router.POST("/loginJSON", func(c *gin.Context) { - var json Login - if err := c.ShouldBindJSON(&json); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if json.User != "manu" || json.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding XML ( - // - // - // user - // 123 - // ) - router.POST("/loginXML", func(c *gin.Context) { - var xml Login - if err := c.ShouldBindXML(&xml); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if xml.User != "manu" || xml.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding a HTML form (user=manu&password=123) - router.POST("/loginForm", func(c *gin.Context) { - var form Login - // This will infer what binder to use depending on the content-type header. - if err := c.ShouldBind(&form); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if form.User != "manu" || form.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") -} -``` - -**测试** -```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"} -``` - -**忽略验证** - -使用`curl` 命令运行上面的例子时,它返回错误,因为这个例子使用`binding:"required"` 。 如果使用 `binding:"-"` ,那么再次运行上面的例子时它不会返回错误。 - -### 自定义验证器 - -注册自定义验证器, 请参阅[示例代码](examples/custom-validation/server.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" -) - -// Booking contains binded and validated data. -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"} -``` - -结构体验证也可以参考[这种方法](https://github.com/go-playground/validator/releases/tag/v8.7)注册。 -请参阅[struct-lvl-validation示例](examples/struct-lvl-validations)以了解更多信息。 - -### 只绑定url查询参数 - -`ShouldBindQuery` 函数只绑定url查询参数而不是post字段。 请参阅[详细信息](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") -} - -``` - -### url查询参数或表单数据绑定到结构体 - -请参阅[详细信息](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292)。 - -```go -package main - -import ( - "log" - "time" - - "github.com/gin-gonic/gin" -) - -type Person struct { - Name string `form:"name"` - Address string `form:"address"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` -} - -func main() { - route := gin.Default() - route.GET("/testing", startPage) - route.Run(":8085") -} - -func startPage(c *gin.Context) { - var person Person - // If `GET`, only `Form` binding engine (`query`) used. - // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). - // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 - if c.ShouldBind(&person) == nil { - log.Println(person.Name) - log.Println(person.Address) - log.Println(person.Birthday) - } - - c.String(200, "Success") -} -``` - -测试: -```sh -$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" -``` -### url路径参数绑定 - -查看[详细信息](https://github.com/gin-gonic/gin/issues/846). - -```go -package main - -import "github.com/gin-gonic/gin" - -type Person struct { - ID string `uri:"id" binding:"required,uuid"` - Name string `uri:"name" binding:"required"` -} - -func main() { - route := gin.Default() - route.GET("/:name/:id", func(c *gin.Context) { - var person Person - if err := c.ShouldBindUri(&person); err != nil { - c.JSON(400, gin.H{"msg": err}) - return - } - c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) - }) - route.Run(":8088") -} -``` - -测试: -```sh -$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 -$ curl -v localhost:8088/thinkerou/not-uuid -``` - -### 绑定 HTML 复选框 - -参见[详细信息](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) - -main.go - -```go -... - -type myForm struct { - Colors []string `form:"colors[]"` -} - -... - -func formHandler(c *gin.Context) { - var fakeForm myForm - c.ShouldBind(&fakeForm) - c.JSON(200, gin.H{"color": fakeForm.Colors}) -} - -... - -``` - -form.html - -```html -
-

Check some colors

- - - - - - - -
-``` - -result: - -``` -{"color":["red","green","blue"]} -``` - -### Multipart Urlencoded 绑定 - -```go -package main - -import ( - "github.com/gin-gonic/gin" -) - -type LoginForm struct { - User string `form:"user" binding:"required"` - Password string `form:"password" binding:"required"` -} - -func main() { - router := gin.Default() - router.POST("/login", func(c *gin.Context) { - // you can bind multipart form with explicit binding declaration: - // c.ShouldBindWith(&form, binding.Form) - // or you can simply use autobinding with ShouldBind method: - var form LoginForm - // in this case proper binding will be automatically selected - if c.ShouldBind(&form) == nil { - if form.User == "user" && form.Password == "password" { - c.JSON(200, gin.H{"status": "you are logged in"}) - } else { - c.JSON(401, gin.H{"status": "unauthorized"}) - } - } - }) - router.Run(":8080") -} -``` - -测试: -```sh -$ curl -v --form user=user --form password=password http://localhost:8080/login -``` - -### XML JSON YAML ProtoBuf 渲染 - -```go -func main() { - r := gin.Default() - - // gin.H is a shortcut for map[string]interface{} - r.GET("/someJSON", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/moreJSON", func(c *gin.Context) { - // You also can use a struct - var msg struct { - Name string `json:"user"` - Message string - Number int - } - msg.Name = "Lena" - msg.Message = "hey" - msg.Number = 123 - // Note that msg.Name becomes "user" in the JSON - // Will output : {"user": "Lena", "Message": "hey", "Number": 123} - c.JSON(http.StatusOK, msg) - }) - - r.GET("/someXML", func(c *gin.Context) { - c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/someYAML", func(c *gin.Context) { - c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/someProtoBuf", func(c *gin.Context) { - reps := []int64{int64(1), int64(2)} - label := "test" - // The specific definition of protobuf is written in the testdata/protoexample file. - data := &protoexample.Test{ - Label: &label, - Reps: reps, - } - // Note that data becomes binary data in the response - // Will output protoexample.Test protobuf serialized data - c.ProtoBuf(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -#### SecureJSON - -使用 SecureJSON 来防止 json 劫持。 如果给定的结构是数组值,则默认预 置`“while(1),”` 到响应体。 - -```go -func main() { - r := gin.Default() - - // You can also use your own secure json prefix - // r.SecureJsonPrefix(")]}',\n") - - r.GET("/someJSON", func(c *gin.Context) { - names := []string{"lena", "austin", "foo"} - - // Will output : while(1);["lena","austin","foo"] - c.SecureJSON(http.StatusOK, names) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` -#### JSONP - -使用 JSONP 从不同域中的服务器请求数据。 如果查询参数回调存在,则将回调添加到响应正文。 - -```go -func main() { - r := gin.Default() - - r.GET("/JSONP?callback=x", func(c *gin.Context) { - data := map[string]interface{}{ - "foo": "bar", - } - - //callback is x - // Will output : x({\"foo\":\"bar\"}) - c.JSONP(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -#### AsciiJSON - -使用 Ascii JSON 生成具有转义的非 ASCII 字符的仅 ASCII JSON。 - -```go -func main() { - r := gin.Default() - - r.GET("/someJSON", func(c *gin.Context) { - data := map[string]interface{}{ - "lang": "GO语言", - "tag": "
", - } - - // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} - c.AsciiJSON(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -#### PureJSON - -通常,JSON使用unicode替换特殊HTML字符,例如 `<` 变为 `\ u003c`。 如果要按字面意思对这些字符进行编码,则可以使用 PureJSON。 -Go 1.6及更低版本无法使用此功能。 - -```go -func main() { - r := gin.Default() - - // Serves unicode entities - r.GET("/json", func(c *gin.Context) { - c.JSON(200, gin.H{ - "html": "Hello, world!", - }) - }) - - // Serves literal characters - r.GET("/purejson", func(c *gin.Context) { - c.PureJSON(200, gin.H{ - "html": "Hello, world!", - }) - }) - - // listen and serve on 0.0.0.0:8080 - r.Run(":8080) -} -``` - -### 静态文件服务 - -```go -func main() { - router := gin.Default() - router.Static("/assets", "./assets") - router.StaticFS("/more_static", http.Dir("my_file_system")) - router.StaticFile("/favicon.ico", "./resources/favicon.ico") - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") -} -``` - -### 从 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 渲染 - -使用LoadHTMLGlob()或LoadHTMLFiles() - -```go -func main() { - router := gin.Default() - router.LoadHTMLGlob("templates/*") - //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") - router.GET("/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "index.tmpl", gin.H{ - "title": "Main website", - }) - }) - router.Run(":8080") -} -``` - -templates/index.tmpl - -```html - -

- {{ .title }} -

- -``` - -在不同目录中使用具有相同名称的模板 - -```go -func main() { - router := gin.Default() - router.LoadHTMLGlob("templates/**/*") - router.GET("/posts/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ - "title": "Posts", - }) - }) - router.GET("/users/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ - "title": "Users", - }) - }) - router.Run(":8080") -} -``` - -templates/posts/index.tmpl - -```html -{{ define "posts/index.tmpl" }} -

- {{ .title }} -

-

Using posts/index.tmpl

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

- {{ .title }} -

-

Using users/index.tmpl

- -{{ end }} -``` - -#### 自定义模板渲染器 - -您还可以使用自己的 html 模板渲染 - -```go -import "html/template" - -func main() { - router := gin.Default() - html := template.Must(template.ParseFiles("file1", "file2")) - router.SetHTMLTemplate(html) - router.Run(":8080") -} -``` - -#### 自定义分隔符 - -您可以使用自定义分隔 - -```go - r := gin.Default() - r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates") -``` - -#### 自定义模板功能 - -查看详细信息[示例代码](examples/template)。 - -main.go - -```go -import ( - "fmt" - "html/template" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -func formatAsDate(t time.Time) string { - year, month, day := t.Date() - return fmt.Sprintf("%d%02d/%02d", year, month, day) -} - -func main() { - router := gin.Default() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("./testdata/template/raw.tmpl") - - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - - router.Run(":8080") -} - -``` - -raw.tmpl - -```html -日期: {[{.now | formatAsDate}]} -``` - -结果: -``` -Date: 2017/07/01 -``` - -### 多模板 - -Gin 允许默认只使用一个 html 模板。 检查[多模板渲染](https://github.com/gin-contrib/multitemplate)以使用 go 1.6 `block template` 等功能。 - -### 重定向 - -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/") -}) -``` - - -发出路由器重定向,使用如下的“HandleContext”。 - -``` 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"}) -}) -``` - - -### 自定义中间件 - -```go -func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - t := time.Now() - - // Set example variable - c.Set("example", "12345") - - // before request - - c.Next() - - // after request - latency := time.Since(t) - log.Print(latency) - - // access the status we are sending - status := c.Writer.Status() - log.Println(status) - } -} - -func main() { - r := gin.New() - r.Use(Logger()) - - r.GET("/test", func(c *gin.Context) { - example := c.MustGet("example").(string) - - // it would print: "12345" - log.Println(example) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### BasicAuth() 中间件 - -```go -// simulate some private data -var secrets = gin.H{ - "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, - "austin": gin.H{"email": "austin@example.com", "phone": "666"}, - "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, -} - -func main() { - r := gin.Default() - - // Group using gin.BasicAuth() middleware - // gin.Accounts is a shortcut for map[string]string - authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ - "foo": "bar", - "austin": "1234", - "lena": "hello2", - "manu": "4321", - })) - - // /admin/secrets endpoint - // hit "localhost:8080/admin/secrets - authorized.GET("/secrets", func(c *gin.Context) { - // get user, it was set by the BasicAuth middleware - user := c.MustGet(gin.AuthUserKey).(string) - if secret, ok := secrets[user]; ok { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) - } else { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) - } - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### 在中间件中使用Goroutines - -当在中间件或handler中启动新的Goroutines时,不能使用原始上下文,必须使用只读副本。 - -```go -func main() { - r := gin.Default() - - r.GET("/long_async", func(c *gin.Context) { - // create copy to be used inside the goroutine - cCp := c.Copy() - go func() { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // note that you are using the copied context "cCp", IMPORTANT - log.Println("Done! in path " + cCp.Request.URL.Path) - }() - }) - - r.GET("/long_sync", func(c *gin.Context) { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // since we are NOT using a goroutine, we do not have to copy the context - log.Println("Done! in path " + c.Request.URL.Path) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### 自定义 HTTP 配置 - -直接使用`http.ListenAndServe()`,如下所示: - -```go -func main() { - router := gin.Default() - http.ListenAndServe(":8080", router) -} -``` -or - -```go -func main() { - router := gin.Default() - - s := &http.Server{ - Addr: ":8080", - Handler: router, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - s.ListenAndServe() -} -``` - -### Let's Encrypt 支持 - -一行代码支持 LetsEncrypt HTTPS示例。 - -[embedmd]:# (examples/auto-tls/example1/main.go go) -```go -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - log.Fatal(autotls.Run(r, "example1.com", "example2.com")) -} -``` - -autocert使用示例。 - -[embedmd]:# (examples/auto-tls/example2/main.go go) -```go -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" - "golang.org/x/crypto/acme/autocert" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), - Cache: autocert.DirCache("/var/www/.cache"), - } - - log.Fatal(autotls.RunWithManager(r, &m)) -} -``` - -### 使用 Gin 运行多个服务 - -请参阅[问题](https://github.com/gin-gonic/gin/issues/346)并尝试以下示例: - -[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) - } -} -``` - -### 优雅重启或停止 - -您想要优雅地重启或停止您的Web服务器吗? -有一些方法可以做到这一点。 - -我们可以使用[fvbock/endless](https://github.com/fvbock/endless)来替换默认的`ListenAndServe`。 有关更多详细信息,请参阅问题[#296](https://github.com/gin-gonic/gin/issues/296)。 - -```go -router := gin.Default() -router.GET("/", handler) -// [...] -endless.ListenAndServe(":4242", router) -``` - -替代方案: - -* [manners](https://github.com/braintree/manners):礼貌的Go HTTP服务器,可以正常关闭。 -* [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 -* [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 - -如果您使用的是Go 1.8,可以考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 -请参阅gin的完整[graceful-shutdown](/examples /graceful-shutdown)示例。 - -[embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) -```go -// +build go1.8 - -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "time" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - router.GET("/", func(c *gin.Context) { - time.Sleep(5 * time.Second) - c.String(http.StatusOK, "Welcome Gin Server") - }) - - srv := &http.Server{ - Addr: ":8080", - Handler: router, - } - - go func() { - // service connections - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) - } - }() - - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 5 seconds. - quit := make(chan os.Signal) - signal.Notify(quit, os.Interrupt) - <-quit - log.Println("Shutdown Server ...") - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server Shutdown:", err) - } - log.Println("Server exiting") -} -``` - -### 静态资源嵌入 - -使用[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 -} -``` - -请参阅`examples/assets-in-binary`目录中的完整示例。 - -### 表单数据绑定到自定义结构体 - -以下示例使用自定义结构体: - -```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() -} -``` - -`curl`命令示例: - -``` -$ 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"}} -``` - -**注意**:不支持以下格式结构体: - -```go -type StructX struct { - X struct {} `form:"name_x"` // HERE have form -} - -type StructY struct { - Y StructX `form:"name_y"` // HERE have form -} - -type StructZ struct { - Z *StructZ `form:"name_z"` // HERE have form -} -``` - -一句话,现在只支持没有`form`的嵌套结构体。 - -### 将request body绑定到不同的结构体中 - -一般通过调用`c.Request.Body`方法绑定数据,但不能多次调用这个方法。 - -```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 { - ... - } -} -``` - -为此,要想多次绑定,需要使用`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`会在绑定之前将body存储到上下文中。 这会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 -* 只有某些格式需要此功能 ,如`JSON`、`XML`、`MsgPack`、`ProtoBuf`。 对于其他格式,如`Query`、`Form`、`FormPost`、`FormMultipart`可以多次调用`c.ShouldBind()`而不会造成任任何性能损失(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 - -### http2 server 推送 - - -http.Pusher仅支持go1.8 +。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 - -[embedmd]:# (examples/http-pusher/main.go go) -```go -package main - -import ( - "html/template" - "log" - - "github.com/gin-gonic/gin" -) - -var html = template.Must(template.New("https").Parse(` - - - Https Test - - - -

Welcome, Ginner!

- - -`)) - -func main() { - r := gin.Default() - r.Static("/assets", "./assets") - r.SetHTMLTemplate(html) - - r.GET("/", func(c *gin.Context) { - if pusher := c.Writer.Pusher(); pusher != nil { - // use pusher.Push() to do server push - if err := pusher.Push("/assets/app.js", nil); err != nil { - log.Printf("Failed to push: %v", err) - } - } - c.HTML(200, "https", gin.H{ - "status": "success", - }) - }) - - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") -} -``` - -### 定义路由日志的格式 - -默认的路由日志格式: -``` -[GIN-debug] POST /foo --> main.main.func1 (3 handlers) -[GIN-debug] GET /bar --> main.main.func2 (3 handlers) -[GIN-debug] GET /status --> main.main.func3 (3 handlers) -``` - -如果要以指的格式(例如JSON,Key Values或其他格式)记录信息,则可以使用`gin.DebugPrintRouteFunc`指定格式。 -在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他满足需求的日志工具。 -```go -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { - log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) - } - - r.POST("/foo", func(c *gin.Context) { - c.JSON(http.StatusOK, "foo") - }) - - r.GET("/bar", func(c *gin.Context) { - c.JSON(http.StatusOK, "bar") - }) - - r.GET("/status", func(c *gin.Context) { - c.JSON(http.StatusOK, "ok") - }) - - // Listen and Server in http://0.0.0.0:8080 - r.Run() -} -``` -### 如何使用Cookie - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - - router.GET("/cookie", func(c *gin.Context) { - - cookie, err := c.Cookie("gin_cookie") - - if err != nil { - cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) - } - - fmt.Printf("Cookie value: %s \n", cookie) - }) - - router.Run() -} -``` - -## 测试 - -HTTP测试首选`net/http/httptest`包。 - -```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") -} -``` - -上面这段代码的测试用例: - -```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()) -} -``` - -## 用户 -使用[Gin](https://github.com/gin-gonic/gin)框架的著名项目: - -* [drone](https://github.com/drone/drone):用Go编写的基于docker的持续集成平台。 -* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务。 -* [fnproject](https://github.com/fnproject/fn):容器驱动、云无关的无服务器平台。 -* [photoprism](https://github.com/photoprism/photoprism): 用Go编写的基于TensorFlow的个人相册管理系统. -* [krakend](https://github.com/devopsfaith/krakend): 表现优异的API网关中间件。 From b97ccf3a43d2901d295870b5876277bf478a4909 Mon Sep 17 00:00:00 2001 From: MetalBreaker Date: Mon, 26 Nov 2018 16:01:51 +0100 Subject: [PATCH 272/912] Router: Route StaticFS() not found to Router's NoRoute() (#1663) Closes #1220 --- routergroup.go | 12 ++++++++++-- routes_test.go | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 9cb0b98..5615a50 100644 --- a/routergroup.go +++ b/routergroup.go @@ -185,11 +185,19 @@ func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRou func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc { absolutePath := group.calculateAbsolutePath(relativePath) fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) - _, nolisting := fs.(*onlyfilesFS) + return func(c *Context) { - if nolisting { + file := c.Param("filepath") + + // Check if file exists and/or if we have permission to access it + if _, err := fs.Open(file); err != nil { c.Writer.WriteHeader(http.StatusNotFound) + c.handlers = group.engine.allNoRoute + // Reset index + c.index = -1 + return } + fileServer.ServeHTTP(c.Writer, c.Request) } } diff --git a/routes_test.go b/routes_test.go index 60f1c81..c4d5972 100644 --- a/routes_test.go +++ b/routes_test.go @@ -411,6 +411,21 @@ func TestRouterNotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, w.Code) } +func TestRouterStaticFSNotFound(t *testing.T) { + router := New() + + router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) + router.NoRoute(func(c *Context) { + c.String(404, "non existent") + }) + + w := performRequest(router, "GET", "/nonexistent") + assert.Equal(t, "non existent", w.Body.String()) + + w = performRequest(router, "HEAD", "/nonexistent") + assert.Equal(t, "non existent", w.Body.String()) +} + func TestRouteRawPath(t *testing.T) { route := New() route.UseRawPath = true From 54e9610400af963d321f8bdd91ce1507f7f8af9f Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 02:02:03 +0800 Subject: [PATCH 273/912] chore: remove wercker yml file (#1676) Now the `wercker.yml` have no longer used. --- wercker.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 wercker.yml diff --git a/wercker.yml b/wercker.yml deleted file mode 100644 index 3ab8084..0000000 --- a/wercker.yml +++ /dev/null @@ -1 +0,0 @@ -box: wercker/default \ No newline at end of file From f463d847c23c85070653fe725cf27172633f57a0 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 05:58:35 +0800 Subject: [PATCH 274/912] chore: fix test fail (#1669) * chore: fix test fail * fix binduri test fail --- binding/binding_test.go | 2 +- recovery_test.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 5b31176..c0204d7 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -687,7 +687,7 @@ func TestUriBinding(t *testing.T) { } var not NotSupportStruct assert.Error(t, b.BindUri(m, ¬)) - assert.Equal(t, "", not.Name) + assert.Equal(t, map[string]interface{}(nil), not.Name) } func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { diff --git a/recovery_test.go b/recovery_test.go index c9fb29c..e886eaa 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -11,6 +11,7 @@ import ( "net" "net/http" "os" + "strings" "syscall" "testing" @@ -84,7 +85,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { const expectCode = 204 expectMsgs := map[syscall.Errno]string{ - syscall.EPIPE: "Broken pipe", + syscall.EPIPE: "broken pipe", syscall.ECONNRESET: "connection reset by peer", } @@ -108,7 +109,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { w := performRequest(router, "GET", "/recovery") // TEST assert.Equal(t, expectCode, w.Code) - assert.Contains(t, buf.String(), expectMsg) + assert.Contains(t, strings.ToLower(buf.String()), expectMsg) }) } } From 98c7ac7202ffc7cfb60706bb48e0ef10f737abb1 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 13:36:49 +0800 Subject: [PATCH 275/912] fix bug (#1682) --- routergroup.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 5615a50..2b41dfd 100644 --- a/routergroup.go +++ b/routergroup.go @@ -187,8 +187,11 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { - file := c.Param("filepath") + if _, nolisting := fs.(*onlyfilesFS); nolisting { + c.Writer.WriteHeader(http.StatusNotFound) + } + file := c.Param("filepath") // Check if file exists and/or if we have permission to access it if _, err := fs.Open(file); err != nil { c.Writer.WriteHeader(http.StatusNotFound) From cce49582d617333c72f4d86abd8ef0e62eb778a0 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 13:49:03 +0800 Subject: [PATCH 276/912] ci: break when test fail (#1671) --- .gitignore | 2 ++ Makefile | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 14dc8f2..bdd50c9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ vendor/* coverage.out count.out test +profile.out +tmp.out diff --git a/Makefile b/Makefile index b698ac0..b0d2e24 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,12 @@ install: deps test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ - $(GO) test -v -covermode=count -coverprofile=profile.out $$d; \ + $(GO) test -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + cat tmp.out; \ + if grep -q "^--- FAIL" tmp.out; then \ + rm tmp.out; \ + exit 1;\ + fi; \ if [ -f profile.out ]; then \ cat profile.out | grep -v "mode:" >> coverage.out; \ rm profile.out; \ From f76ccb25f1eee8684e545ec00f8f2934fec98f09 Mon Sep 17 00:00:00 2001 From: Sai Date: Wed, 12 Dec 2018 10:05:16 +0900 Subject: [PATCH 277/912] Add LoggerWithFormatter method (#1677) * Add LoggerWithFormatter * Add tests for LoggerWithFormatter & LoggerWithConfig * Add note for README * Add tests for DefaultLogFormatter * Add comment * Change DefaultLogFormatter to a private method --- README.md | 38 ++++++++++ logger.go | 116 +++++++++++++++++++++++------ logger_test.go | 194 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 324 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index e7b92b2..c1f902a 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - [Using middleware](#using-middleware) - [How to write log file](#how-to-write-log-file) + - [Custom Log Format](#custom-log-format) - [Model binding and validation](#model-binding-and-validation) - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) @@ -528,6 +529,43 @@ func main() { } ``` +### Custom Log Format +```go +func main() { + router := gin.New() + + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + + // your custom format + return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", + param.ClientIP, + param.TimeStamp.Format(time.RFC1123), + param.Method, + param.Path, + param.Request.Proto, + param.StatusCode, + param.Latency, + param.Request.UserAgent(), + param.ErrorMessage, + ) + })) + router.Use(gin.Recovery()) + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +**Sample Output** +``` +::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " +``` + ### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). diff --git a/logger.go b/logger.go index 74dd2e6..a64af69 100644 --- a/logger.go +++ b/logger.go @@ -26,6 +26,56 @@ var ( disableColor = false ) +// LoggerConfig defines the config for Logger middleware. +type LoggerConfig struct { + // Optional. Default value is gin.defaultLogFormatter + Formatter LogFormatter + + // Output is a writer where logs are written. + // Optional. Default value is gin.DefaultWriter. + Output io.Writer + + // SkipPathes is a url path array which logs are not written. + // Optional. + SkipPathes []string +} + +// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter +type LogFormatter func(params LogFormatterParams) string + +// LogFormatterParams is the structure any formatter will be handed when time to log comes +type LogFormatterParams struct { + Request *http.Request + TimeStamp time.Time + StatusCode int + Latency time.Duration + ClientIP string + Method string + Path string + ErrorMessage string + IsTerm bool +} + +// defaultLogFormatter is the default log format function Logger middleware uses. +var defaultLogFormatter = func(param LogFormatterParams) string { + var statusColor, methodColor, resetColor string + if param.IsTerm { + statusColor = colorForStatus(param.StatusCode) + methodColor = colorForMethod(param.Method) + resetColor = reset + } + + return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", + param.TimeStamp.Format("2006/01/02 - 15:04:05"), + statusColor, param.StatusCode, resetColor, + param.Latency, + param.ClientIP, + methodColor, param.Method, resetColor, + param.Path, + param.ErrorMessage, + ) +} + // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { disableColor = true @@ -50,12 +100,39 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc { // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. // By default gin.DefaultWriter = os.Stdout. func Logger() HandlerFunc { - return LoggerWithWriter(DefaultWriter) + return LoggerWithConfig(LoggerConfig{}) +} + +// LoggerWithFormatter instance a Logger middleware with the specified log format function. +func LoggerWithFormatter(f LogFormatter) HandlerFunc { + return LoggerWithConfig(LoggerConfig{ + Formatter: f, + }) } // LoggerWithWriter instance a Logger middleware with the specified writer buffer. // Example: os.Stdout, a file opened in write mode, a socket... func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { + return LoggerWithConfig(LoggerConfig{ + Output: out, + SkipPathes: notlogged, + }) +} + +// LoggerWithConfig instance a Logger middleware with config. +func LoggerWithConfig(conf LoggerConfig) HandlerFunc { + formatter := conf.Formatter + if formatter == nil { + formatter = defaultLogFormatter + } + + out := conf.Output + if out == nil { + out = DefaultWriter + } + + notlogged := conf.SkipPathes + isTerm := true if w, ok := out.(*os.File); !ok || @@ -85,34 +162,27 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { // Log only when path is not being skipped if _, ok := skip[path]; !ok { - // Stop timer - end := time.Now() - latency := end.Sub(start) - - clientIP := c.ClientIP() - method := c.Request.Method - statusCode := c.Writer.Status() - var statusColor, methodColor, resetColor string - if isTerm { - statusColor = colorForStatus(statusCode) - methodColor = colorForMethod(method) - resetColor = reset + param := LogFormatterParams{ + Request: c.Request, + IsTerm: isTerm, } - comment := c.Errors.ByType(ErrorTypePrivate).String() + + // Stop timer + param.TimeStamp = time.Now() + param.Latency = param.TimeStamp.Sub(start) + + param.ClientIP = c.ClientIP() + param.Method = c.Request.Method + param.StatusCode = c.Writer.Status() + param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() if raw != "" { path = path + "?" + raw } - fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", - end.Format("2006/01/02 - 15:04:05"), - statusColor, statusCode, resetColor, - latency, - clientIP, - methodColor, method, resetColor, - path, - comment, - ) + param.Path = path + + fmt.Fprintf(out, formatter(param)) } } } diff --git a/logger_test.go b/logger_test.go index 6118cb0..909ddd3 100644 --- a/logger_test.go +++ b/logger_test.go @@ -7,8 +7,10 @@ package gin import ( "bytes" "errors" + "fmt" "net/http" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -79,7 +81,179 @@ func TestLogger(t *testing.T) { assert.Contains(t, buffer.String(), "404") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/notfound") +} + +func TestLoggerWithConfig(t *testing.T) { + buffer := new(bytes.Buffer) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{Output: buffer})) + router.GET("/example", func(c *Context) {}) + router.POST("/example", func(c *Context) {}) + router.PUT("/example", func(c *Context) {}) + router.DELETE("/example", func(c *Context) {}) + router.PATCH("/example", func(c *Context) {}) + router.HEAD("/example", func(c *Context) {}) + router.OPTIONS("/example", func(c *Context) {}) + + performRequest(router, "GET", "/example?a=100") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") + + // I wrote these first (extending the above) but then realized they are more + // like integration tests because they test the whole logging process rather + // than individual functions. Im not sure where these should go. + buffer.Reset() + performRequest(router, "POST", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "POST") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "PUT", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "PUT") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "DELETE", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "DELETE") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "PATCH", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "PATCH") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "HEAD", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "HEAD") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "OPTIONS", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "OPTIONS") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "GET", "/notfound") + assert.Contains(t, buffer.String(), "404") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/notfound") +} + +func TestLoggerWithFormatter(t *testing.T) { + buffer := new(bytes.Buffer) + + d := DefaultWriter + DefaultWriter = buffer + defer func() { + DefaultWriter = d + }() + + router := New() + router.Use(LoggerWithFormatter(func(param LogFormatterParams) string { + return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s", + param.TimeStamp.Format("2006/01/02 - 15:04:05"), + param.StatusCode, + param.Latency, + param.ClientIP, + param.Method, + param.Path, + param.ErrorMessage, + ) + })) + router.GET("/example", func(c *Context) {}) + performRequest(router, "GET", "/example?a=100") + + // output test + assert.Contains(t, buffer.String(), "[FORMATTER TEST]") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") +} + +func TestLoggerWithConfigFormatting(t *testing.T) { + var gotParam LogFormatterParams + buffer := new(bytes.Buffer) + + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + Formatter: func(param LogFormatterParams) string { + // for assert test + gotParam = param + + return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s", + param.TimeStamp.Format("2006/01/02 - 15:04:05"), + param.StatusCode, + param.Latency, + param.ClientIP, + param.Method, + param.Path, + param.ErrorMessage, + ) + }, + })) + router.GET("/example", func(c *Context) { + // set dummy ClientIP + c.Request.Header.Set("X-Forwarded-For", "20.20.20.20") + }) + performRequest(router, "GET", "/example?a=100") + + // output test + assert.Contains(t, buffer.String(), "[FORMATTER TEST]") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") + + // LogFormatterParams test + assert.NotNil(t, gotParam.Request) + assert.NotEmpty(t, gotParam.TimeStamp) + assert.Equal(t, 200, gotParam.StatusCode) + assert.NotEmpty(t, gotParam.Latency) + assert.Equal(t, "20.20.20.20", gotParam.ClientIP) + assert.Equal(t, "GET", gotParam.Method) + assert.Equal(t, "/example?a=100", gotParam.Path) + assert.Empty(t, gotParam.ErrorMessage) + +} + +func TestDefaultLogFormatter(t *testing.T) { + timeStamp := time.Unix(1544173902, 0).UTC() + + termFalseParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Second * 5, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + IsTerm: false, + } + + termTrueParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Second * 5, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + IsTerm: true, + } + + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam)) } func TestColorForMethod(t *testing.T) { @@ -127,7 +301,7 @@ func TestErrorLogger(t *testing.T) { assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } -func TestSkippingPaths(t *testing.T) { +func TestLoggerWithWriterSkippingPaths(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithWriter(buffer, "/skipped")) @@ -142,6 +316,24 @@ func TestSkippingPaths(t *testing.T) { assert.Contains(t, buffer.String(), "") } +func TestLoggerWithConfigSkippingPaths(t *testing.T) { + buffer := new(bytes.Buffer) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + SkipPathes: []string{"/skipped"}, + })) + router.GET("/logged", func(c *Context) {}) + router.GET("/skipped", func(c *Context) {}) + + performRequest(router, "GET", "/logged") + assert.Contains(t, buffer.String(), "200") + + buffer.Reset() + performRequest(router, "GET", "/skipped") + assert.Contains(t, buffer.String(), "") +} + func TestDisableConsoleColor(t *testing.T) { New() assert.False(t, disableColor) From 59695e7ba86dec1a8d847c3329a3e7c9f7705125 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 12 Dec 2018 23:40:29 +0800 Subject: [PATCH 278/912] Add BindUri (#1694) * add BindUri * fix bug * fix code style --- context.go | 18 ++++++++++++++---- githubapi_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 478e8c0..c94926e 100644 --- a/context.go +++ b/context.go @@ -530,15 +530,25 @@ func (c *Context) BindYAML(obj interface{}) error { return c.MustBindWith(obj, binding.YAML) } +// BindUri binds the passed struct pointer using binding.Uri. +// It will abort the request with HTTP 400 if any error occurs. +func (c *Context) BindUri(obj interface{}) error { + if err := c.ShouldBindUri(obj); err != nil { + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + return err + } + return nil +} + // MustBindWith binds the passed struct pointer using the specified binding engine. // It will abort the request with HTTP 400 if any error occurs. // See the binding package. -func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { - if err = c.ShouldBindWith(obj, b); err != nil { +func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { + if err := c.ShouldBindWith(obj, b); err != nil { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + return err } - - return + return nil } // ShouldBind checks the Content-Type to select a binding engine automatically, diff --git a/githubapi_test.go b/githubapi_test.go index 6b56a2b..5253425 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -290,8 +290,8 @@ func TestShouldBindUri(t *testing.T) { router := Default() type Person struct { - Name string `uri:"name"` - Id string `uri:"id"` + Name string `uri:"name" binding:"required"` + Id string `uri:"id" binding:"required"` } router.Handle("GET", "/rest/:name/:id", func(c *Context) { var person Person @@ -304,6 +304,46 @@ func TestShouldBindUri(t *testing.T) { path, _ := exampleFromPath("/rest/:name/:id") w := performRequest(router, "GET", path) assert.Equal(t, "ShouldBindUri test OK", w.Body.String()) + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestBindUri(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + + type Person struct { + Name string `uri:"name" binding:"required"` + Id string `uri:"id" binding:"required"` + } + router.Handle("GET", "/rest/:name/:id", func(c *Context) { + var person Person + assert.NoError(t, c.BindUri(&person)) + assert.True(t, "" != person.Name) + assert.True(t, "" != person.Id) + c.String(http.StatusOK, "BindUri test OK") + }) + + path, _ := exampleFromPath("/rest/:name/:id") + w := performRequest(router, "GET", path) + assert.Equal(t, "BindUri test OK", w.Body.String()) + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestBindUriError(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + + type Member struct { + Number string `uri:"num" binding:"required,uuid"` + } + router.Handle("GET", "/new/rest/:num", func(c *Context) { + var m Member + c.BindUri(&m) + }) + + path1, _ := exampleFromPath("/new/rest/:num") + w1 := performRequest(router, "GET", path1) + assert.Equal(t, http.StatusBadRequest, w1.Code) } func githubConfigRouter(router *Engine) { From f67d7a90c4d2e5bdf310a78d7e6a04e3d9aee851 Mon Sep 17 00:00:00 2001 From: Romain Beuque Date: Thu, 13 Dec 2018 12:20:17 +0900 Subject: [PATCH 279/912] context: inherits context cancelation and deadline from http.Request context for Go>=1.7 (#1690) *gin.Context implements standard context.Context methods, but always returns data as context is still valid. Since Go 1.7, http.Request now contains a context.Context object, which can be controlled by the http.Server to indicates that the context is now closed, and persue of request should be canceled. This implements the propagation of http.Request context methods inside gin.Context to have HTTP context cancelation information at gin.Context level. Signed-off-by: Romain Beuque --- context.go | 28 -------------------------- context_17.go | 30 ++++++++++++++++++++++++++++ context_17_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ context_pre17.go | 39 ++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 28 deletions(-) create mode 100644 context_pre17.go diff --git a/context.go b/context.go index c94926e..c38b2b8 100644 --- a/context.go +++ b/context.go @@ -942,34 +942,6 @@ func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. -func (c *Context) Deadline() (deadline time.Time, ok bool) { - return -} - -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. -func (c *Context) Done() <-chan struct{} { - return nil -} - -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. -func (c *Context) Err() error { - return nil -} - // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. diff --git a/context_17.go b/context_17.go index 8e9f75a..024dcb7 100644 --- a/context_17.go +++ b/context_17.go @@ -7,6 +7,8 @@ package gin import ( + "time" + "github.com/gin-gonic/gin/render" ) @@ -15,3 +17,31 @@ import ( func (c *Context) PureJSON(code int, obj interface{}) { c.Render(code, render.PureJSON{Data: obj}) } + +/************************************/ +/***** GOLANG.ORG/X/NET/CONTEXT *****/ +/************************************/ + +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. +func (c *Context) Deadline() (time.Time, bool) { + return c.Request.Context().Deadline() +} + +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. +func (c *Context) Done() <-chan struct{} { + return c.Request.Context().Done() +} + +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. +func (c *Context) Err() error { + return c.Request.Context().Err() +} diff --git a/context_17_test.go b/context_17_test.go index 5b9ebcd..f2a2f18 100644 --- a/context_17_test.go +++ b/context_17_test.go @@ -7,9 +7,12 @@ package gin import ( + "bytes" + "context" "net/http" "net/http/httptest" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -25,3 +28,49 @@ func TestContextRenderPureJSON(t *testing.T) { assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } + +func TestContextHTTPContext(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + c.Request = req.WithContext(ctx) + + assert.NoError(t, c.Err()) + assert.NotNil(t, c.Done()) + select { + case <-c.Done(): + assert.Fail(t, "context should not be canceled") + default: + } + + ti, ok := c.Deadline() + assert.Equal(t, ti, time.Time{}) + assert.False(t, ok) + assert.Equal(t, c.Value(0), c.Request) + + cancelFunc() + assert.NotNil(t, c.Done()) + select { + case <-c.Done(): + default: + assert.Fail(t, "context should be canceled") + } +} + +func TestContextHTTPContextWithDeadline(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + location, _ := time.LoadLocation("Europe/Paris") + assert.NotNil(t, location) + date := time.Date(2031, 12, 27, 16, 00, 00, 00, location) + ctx, cancelFunc := context.WithDeadline(context.Background(), date) + defer cancelFunc() + c.Request = req.WithContext(ctx) + + assert.NoError(t, c.Err()) + + ti, ok := c.Deadline() + assert.Equal(t, ti, date) + assert.True(t, ok) +} diff --git a/context_pre17.go b/context_pre17.go new file mode 100644 index 0000000..2008d3c --- /dev/null +++ b/context_pre17.go @@ -0,0 +1,39 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !go1.7 + +package gin + +import ( + "time" +) + +/************************************/ +/***** GOLANG.ORG/X/NET/CONTEXT *****/ +/************************************/ + +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. +func (c *Context) Deadline() (deadline time.Time, ok bool) { + return +} + +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. +func (c *Context) Done() <-chan struct{} { + return nil +} + +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. +func (c *Context) Err() error { + return nil +} From 1542eff27f7d67ef56543358e2f623eb4ea8adf9 Mon Sep 17 00:00:00 2001 From: Ganlv Date: Mon, 17 Dec 2018 08:13:07 +0800 Subject: [PATCH 280/912] Fix #1693: file.Filename should not be trusted (#1699) --- README.md | 4 ++++ examples/upload-file/multiple/main.go | 4 +++- examples/upload-file/single/main.go | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1f902a..2dc9e5f 100644 --- a/README.md +++ b/README.md @@ -364,6 +364,10 @@ ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single). +`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) + +> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. + ```go func main() { router := gin.Default() diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go index a55325e..2b9d6d9 100644 --- a/examples/upload-file/multiple/main.go +++ b/examples/upload-file/multiple/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/http" + "path/filepath" "github.com/gin-gonic/gin" ) @@ -25,7 +26,8 @@ func main() { files := form.File["files"] for _, file := range files { - if err := c.SaveUploadedFile(file, file.Filename); err != nil { + filename := filepath.Base(file.Filename) + if err := c.SaveUploadedFile(file, filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go index 5d43865..ba289f5 100644 --- a/examples/upload-file/single/main.go +++ b/examples/upload-file/single/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/http" + "path/filepath" "github.com/gin-gonic/gin" ) @@ -23,7 +24,8 @@ func main() { return } - if err := c.SaveUploadedFile(file, file.Filename); err != nil { + filename := filepath.Base(file.Filename) + if err := c.SaveUploadedFile(file, filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } From 678e09c736505225e28d8c585087b84faaf4bb80 Mon Sep 17 00:00:00 2001 From: Sai Date: Thu, 20 Dec 2018 18:54:08 +0900 Subject: [PATCH 281/912] Plural is "Paths", not "Pathes" (#1706) --- logger.go | 10 +++++----- logger_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/logger.go b/logger.go index a64af69..b9c63c7 100644 --- a/logger.go +++ b/logger.go @@ -35,9 +35,9 @@ type LoggerConfig struct { // Optional. Default value is gin.DefaultWriter. Output io.Writer - // SkipPathes is a url path array which logs are not written. + // SkipPaths is a url path array which logs are not written. // Optional. - SkipPathes []string + SkipPaths []string } // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter @@ -114,8 +114,8 @@ func LoggerWithFormatter(f LogFormatter) HandlerFunc { // Example: os.Stdout, a file opened in write mode, a socket... func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { return LoggerWithConfig(LoggerConfig{ - Output: out, - SkipPathes: notlogged, + Output: out, + SkipPaths: notlogged, }) } @@ -131,7 +131,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { out = DefaultWriter } - notlogged := conf.SkipPathes + notlogged := conf.SkipPaths isTerm := true diff --git a/logger_test.go b/logger_test.go index 909ddd3..350599d 100644 --- a/logger_test.go +++ b/logger_test.go @@ -320,8 +320,8 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithConfig(LoggerConfig{ - Output: buffer, - SkipPathes: []string{"/skipped"}, + Output: buffer, + SkipPaths: []string{"/skipped"}, })) router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) From 2d33c82028b4085827137c5a72cc0a076d8e2b08 Mon Sep 17 00:00:00 2001 From: Sai Date: Wed, 26 Dec 2018 00:27:24 +0900 Subject: [PATCH 282/912] Add comment to LogFormatterParams struct's fields (#1711) By https://github.com/gin-gonic/gin/issues/1701, I thought it's necessary. --- logger.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/logger.go b/logger.go index b9c63c7..a55d26e 100644 --- a/logger.go +++ b/logger.go @@ -45,15 +45,24 @@ type LogFormatter func(params LogFormatterParams) string // LogFormatterParams is the structure any formatter will be handed when time to log comes type LogFormatterParams struct { - Request *http.Request - TimeStamp time.Time - StatusCode int - Latency time.Duration - ClientIP string - Method string - Path string + Request *http.Request + + // TimeStamp shows the time after the server returns a response. + TimeStamp time.Time + // StatusCode is HTTP response code. + StatusCode int + // Latency is how much time the server cost to process a certain request. + Latency time.Duration + // ClientIP equals Context's ClientIP method. + ClientIP string + // Method is the HTTP method given to the request. + Method string + // Path is a path the client requests. + Path string + // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string - IsTerm bool + // IsTerm shows whether does gin's output descriptor refers to a terminal. + IsTerm bool } // defaultLogFormatter is the default log format function Logger middleware uses. From 1b34e8e8de41654004dfabf20fbadf43f619d41b Mon Sep 17 00:00:00 2001 From: thinkerou Date: Tue, 25 Dec 2018 23:40:11 +0800 Subject: [PATCH 283/912] chore: attemp to fix #1700 (#1707) --- tools.go => tools/tools.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tools.go => tools/tools.go (88%) diff --git a/tools.go b/tools/tools.go similarity index 88% rename from tools.go rename to tools/tools.go index 9f96406..7113e71 100644 --- a/tools.go +++ b/tools/tools.go @@ -4,12 +4,12 @@ // +build tools -// This file exists to cause `go mod` and `go get` to believe these tools +// This package exists to cause `go mod` and `go get` to believe these tools // are dependencies, even though they are not runtime dependencies of any // gin package. This means they will appear in `go.mod` file, but will not // be a part of the build. -package gin +package tools import ( _ "github.com/campoy/embedmd" From 0bfc9cbcdbaa13e5fd633f77a32f22c30f1e2553 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 26 Dec 2018 00:27:46 +0800 Subject: [PATCH 284/912] ci: exit 1 when build fail (#1695) Like this: ``` FAIL github.com/gin-gonic/gin [build failed] ``` --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b0d2e24..7211144 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,10 @@ test: cat tmp.out; \ if grep -q "^--- FAIL" tmp.out; then \ rm tmp.out; \ - exit 1;\ + exit 1; \ + elif grep -q "build failed" tmp.out; then \ + rm tmp.out; \ + exit; \ fi; \ if [ -f profile.out ]; then \ cat profile.out | grep -v "mode:" >> coverage.out; \ From 49e4b0c60cb533e943c34a8d637944f25fa47ee6 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 28 Dec 2018 04:57:09 +0300 Subject: [PATCH 285/912] fix mapping inner structs with correct tag (#1718) --- binding/binding_test.go | 23 +++++++++++++++++++++++ binding/form_mapping.go | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index c0204d7..740749b 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "mime/multipart" "net/http" + "strconv" "testing" "time" @@ -690,6 +691,28 @@ func TestUriBinding(t *testing.T) { assert.Equal(t, map[string]interface{}(nil), not.Name) } +func TestUriInnerBinding(t *testing.T) { + type Tag struct { + Name string `uri:"name"` + S struct { + Age int `uri:"age"` + } + } + + expectedName := "mike" + expectedAge := 25 + + m := map[string][]string{ + "name": {expectedName}, + "age": {strconv.Itoa(expectedAge)}, + } + + var tag Tag + assert.NoError(t, Uri.BindUri(m, &tag)) + assert.Equal(t, tag.Name, expectedName) + assert.Equal(t, tag.S.Age, expectedAge) +} + func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index d893c21..8900ab7 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -55,7 +55,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { structFieldKind = structField.Kind() } if structFieldKind == reflect.Struct { - err := mapForm(structField.Addr().Interface(), form) + err := mapFormByTag(structField.Addr().Interface(), form, tag) if err != nil { return err } From 807368579f8939cedfa59ec689708754920f93e4 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 28 Dec 2018 05:26:29 +0300 Subject: [PATCH 286/912] fix test - auto choose port number (#1719) --- gin_integration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index e14a688..01d5cf5 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -137,7 +137,7 @@ func TestBadUnixSocket(t *testing.T) { func TestFileDescriptor(t *testing.T) { router := New() - addr, err := net.ResolveTCPAddr("tcp", ":8000") + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") assert.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) assert.NoError(t, err) @@ -152,7 +152,7 @@ func TestFileDescriptor(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - c, err := net.Dial("tcp", "localhost:8000") + c, err := net.Dial("tcp", listener.Addr().String()) assert.NoError(t, err) fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") From 85b92cdf4bc9bf33fd6f199ff866a1eed3511c80 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 29 Dec 2018 11:46:26 +0800 Subject: [PATCH 287/912] chore(testing): case sensitive for query string (#1720) fix #1692 --- context_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index dced73f..836b3ec 100644 --- a/context_test.go +++ b/context_test.go @@ -1457,7 +1457,7 @@ func TestContextShouldBindWithXML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` FOO - BAR + BAR `)) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type @@ -1475,15 +1475,19 @@ func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) var obj struct { - Foo string `form:"foo"` - Bar string `form:"bar"` + Foo string `form:"foo"` + Bar string `form:"bar"` + Foo1 string `form:"Foo"` + Bar1 string `form:"Bar"` } assert.NoError(t, c.ShouldBindQuery(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo1", obj.Bar1) + assert.Equal(t, "bar1", obj.Foo1) assert.Equal(t, 0, w.Body.Len()) } From d8fb18c33b1657271b6302e0899d033902012f49 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Mon, 31 Dec 2018 11:02:53 +1000 Subject: [PATCH 288/912] Fix case of GitHub (#1726) --- routergroup.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 2b41dfd..297d357 100644 --- a/routergroup.go +++ b/routergroup.go @@ -47,7 +47,7 @@ type RouterGroup struct { var _ IRouter = &RouterGroup{} -// Use adds middleware to the group, see example code in github. +// Use adds middleware to the group, see example code in GitHub. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() @@ -78,7 +78,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl // Handle registers a new request handle and middleware with the given path and method. // The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. -// See the example code in github. +// See the example code in GitHub. // // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut // functions can be used. From 29a145c85dc0fafc3dd0ada62d856c4d95240010 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 9 Jan 2019 09:32:44 +0800 Subject: [PATCH 289/912] Revert "context: inherits context cancelation and deadline from http.Request context for Go>=1.7 (#1690)" (#1736) This reverts commit f67d7a90c4d2e5bdf310a78d7e6a04e3d9aee851. --- context.go | 28 ++++++++++++++++++++++++++ context_17.go | 30 ---------------------------- context_17_test.go | 49 ---------------------------------------------- context_pre17.go | 39 ------------------------------------ 4 files changed, 28 insertions(+), 118 deletions(-) delete mode 100644 context_pre17.go diff --git a/context.go b/context.go index c38b2b8..c94926e 100644 --- a/context.go +++ b/context.go @@ -942,6 +942,34 @@ func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } +/************************************/ +/***** GOLANG.ORG/X/NET/CONTEXT *****/ +/************************************/ + +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. +func (c *Context) Deadline() (deadline time.Time, ok bool) { + return +} + +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. +func (c *Context) Done() <-chan struct{} { + return nil +} + +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. +func (c *Context) Err() error { + return nil +} + // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. diff --git a/context_17.go b/context_17.go index 024dcb7..8e9f75a 100644 --- a/context_17.go +++ b/context_17.go @@ -7,8 +7,6 @@ package gin import ( - "time" - "github.com/gin-gonic/gin/render" ) @@ -17,31 +15,3 @@ import ( func (c *Context) PureJSON(code int, obj interface{}) { c.Render(code, render.PureJSON{Data: obj}) } - -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. -func (c *Context) Deadline() (time.Time, bool) { - return c.Request.Context().Deadline() -} - -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. -func (c *Context) Done() <-chan struct{} { - return c.Request.Context().Done() -} - -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. -func (c *Context) Err() error { - return c.Request.Context().Err() -} diff --git a/context_17_test.go b/context_17_test.go index f2a2f18..5b9ebcd 100644 --- a/context_17_test.go +++ b/context_17_test.go @@ -7,12 +7,9 @@ package gin import ( - "bytes" - "context" "net/http" "net/http/httptest" "testing" - "time" "github.com/stretchr/testify/assert" ) @@ -28,49 +25,3 @@ func TestContextRenderPureJSON(t *testing.T) { assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } - -func TestContextHTTPContext(t *testing.T) { - c, _ := CreateTestContext(httptest.NewRecorder()) - req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) - ctx, cancelFunc := context.WithCancel(context.Background()) - defer cancelFunc() - c.Request = req.WithContext(ctx) - - assert.NoError(t, c.Err()) - assert.NotNil(t, c.Done()) - select { - case <-c.Done(): - assert.Fail(t, "context should not be canceled") - default: - } - - ti, ok := c.Deadline() - assert.Equal(t, ti, time.Time{}) - assert.False(t, ok) - assert.Equal(t, c.Value(0), c.Request) - - cancelFunc() - assert.NotNil(t, c.Done()) - select { - case <-c.Done(): - default: - assert.Fail(t, "context should be canceled") - } -} - -func TestContextHTTPContextWithDeadline(t *testing.T) { - c, _ := CreateTestContext(httptest.NewRecorder()) - req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) - location, _ := time.LoadLocation("Europe/Paris") - assert.NotNil(t, location) - date := time.Date(2031, 12, 27, 16, 00, 00, 00, location) - ctx, cancelFunc := context.WithDeadline(context.Background(), date) - defer cancelFunc() - c.Request = req.WithContext(ctx) - - assert.NoError(t, c.Err()) - - ti, ok := c.Deadline() - assert.Equal(t, ti, date) - assert.True(t, ok) -} diff --git a/context_pre17.go b/context_pre17.go deleted file mode 100644 index 2008d3c..0000000 --- a/context_pre17.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build !go1.7 - -package gin - -import ( - "time" -) - -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. -func (c *Context) Deadline() (deadline time.Time, ok bool) { - return -} - -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. -func (c *Context) Done() <-chan struct{} { - return nil -} - -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. -func (c *Context) Err() error { - return nil -} From b056a34bdc2aa45256c4f5bdad306c35ec70c37e Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 18 Jan 2019 04:32:53 +0300 Subject: [PATCH 290/912] fix errcheck warnings (#1739) --- binding/binding_test.go | 24 +++++++++++----------- binding/form.go | 6 +++++- context.go | 22 +++++++++++++------- context_test.go | 36 +++++++++++++++++++-------------- debug_test.go | 19 ++++++++--------- errors_test.go | 6 +++--- gin.go | 5 ++++- gin_integration_test.go | 2 +- githubapi_test.go | 2 +- logger_test.go | 6 +++--- middleware_test.go | 2 +- recovery.go | 2 +- render/json.go | 45 +++++++++++++++++++++++++++-------------- render/protobuf.go | 4 ++-- render/render_test.go | 4 ++-- render/text.go | 10 ++++----- render/yaml.go | 4 ++-- response_writer_test.go | 3 ++- routes_test.go | 3 ++- 19 files changed, 122 insertions(+), 83 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 740749b..1044e6c 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -516,28 +516,28 @@ func createFormPostRequestFail() *http.Request { return req } -func createFormMultipartRequest() *http.Request { +func createFormMultipartRequest(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() - mw.SetBoundary(boundary) - mw.WriteField("foo", "bar") - mw.WriteField("bar", "foo") + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } -func createFormMultipartRequestFail() *http.Request { +func createFormMultipartRequestFail(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() - mw.SetBoundary(boundary) - mw.WriteField("map_foo", "bar") + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("map_foo", "bar")) req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -546,7 +546,7 @@ func createFormMultipartRequestFail() *http.Request { func TestBindingFormPost(t *testing.T) { req := createFormPostRequest() var obj FooBarStruct - FormPost.Bind(req, &obj) + assert.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "form-urlencoded", FormPost.Name()) assert.Equal(t, "bar", obj.Foo) @@ -556,7 +556,7 @@ func TestBindingFormPost(t *testing.T) { func TestBindingDefaultValueFormPost(t *testing.T) { req := createDefaultFormPostRequest() var obj FooDefaultBarStruct - FormPost.Bind(req, &obj) + assert.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) @@ -570,9 +570,9 @@ func TestBindingFormPostFail(t *testing.T) { } func TestBindingFormMultipart(t *testing.T) { - req := createFormMultipartRequest() + req := createFormMultipartRequest(t) var obj FooBarStruct - FormMultipart.Bind(req, &obj) + assert.NoError(t, FormMultipart.Bind(req, &obj)) assert.Equal(t, "multipart/form-data", FormMultipart.Name()) assert.Equal(t, "bar", obj.Foo) @@ -580,7 +580,7 @@ func TestBindingFormMultipart(t *testing.T) { } func TestBindingFormMultipartFail(t *testing.T) { - req := createFormMultipartRequestFail() + req := createFormMultipartRequestFail(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) assert.Error(t, err) diff --git a/binding/form.go b/binding/form.go index 0be5966..8955c95 100644 --- a/binding/form.go +++ b/binding/form.go @@ -20,7 +20,11 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } - req.ParseMultipartForm(defaultMemory) + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } if err := mapForm(obj, req.Form); err != nil { return err } diff --git a/context.go b/context.go index c94926e..4dc94ea 100644 --- a/context.go +++ b/context.go @@ -415,7 +415,11 @@ func (c *Context) PostFormArray(key string) []string { // a boolean value whether at least one value exists for the given key. func (c *Context) GetPostFormArray(key string) ([]string, bool) { req := c.Request - req.ParseMultipartForm(c.engine.MaxMultipartMemory) + if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + if err != http.ErrNotMultipart { + debugPrint("error on parse multipart form array: %v", err) + } + } if values := req.PostForm[key]; len(values) > 0 { return values, true } @@ -437,7 +441,11 @@ func (c *Context) PostFormMap(key string) map[string]string { // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { req := c.Request - req.ParseMultipartForm(c.engine.MaxMultipartMemory) + if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + if err != http.ErrNotMultipart { + debugPrint("error on parse multipart form map: %v", err) + } + } dicts, exist := c.get(req.PostForm, key) if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil { @@ -493,8 +501,8 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error } defer out.Close() - io.Copy(out, src) - return nil + _, err = io.Copy(out, src) + return err } // Bind checks the Content-Type to select a binding engine automatically, @@ -534,7 +542,7 @@ func (c *Context) BindYAML(obj interface{}) error { // It will abort the request with HTTP 400 if any error occurs. func (c *Context) BindUri(obj interface{}) error { if err := c.ShouldBindUri(obj); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err } return nil @@ -545,7 +553,7 @@ func (c *Context) BindUri(obj interface{}) error { // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { if err := c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err } return nil @@ -913,7 +921,7 @@ func (c *Context) Negotiate(code int, config Negotiate) { c.XML(code, data) default: - c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) + c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck } } diff --git a/context_test.go b/context_test.go index 836b3ec..29ec1a2 100644 --- a/context_test.go +++ b/context_test.go @@ -70,7 +70,8 @@ func TestContextFormFile(t *testing.T) { mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { - w.Write([]byte("test")) + _, err = w.Write([]byte("test")) + assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) @@ -100,10 +101,11 @@ func TestContextFormFileFailed(t *testing.T) { func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) - mw.WriteField("foo", "bar") + assert.NoError(t, mw.WriteField("foo", "bar")) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { - w.Write([]byte("test")) + _, err = w.Write([]byte("test")) + assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) @@ -137,7 +139,8 @@ func TestSaveUploadedCreateFailed(t *testing.T) { mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { - w.Write([]byte("test")) + _, err = w.Write([]byte("test")) + assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) @@ -159,7 +162,7 @@ func TestContextReset(t *testing.T) { c.index = 2 c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()} c.Params = Params{Param{}} - c.Error(errors.New("test")) + c.Error(errors.New("test")) // nolint: errcheck c.Set("foo", "bar") c.reset() @@ -798,7 +801,7 @@ func TestContextRenderHTML2(t *testing.T) { assert.Len(t, router.trees, 1) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) router.SetHTMLTemplate(templ) SetMode(TestMode) @@ -1211,7 +1214,8 @@ func TestContextAbortWithStatusJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", contentType) buf := new(bytes.Buffer) - buf.ReadFrom(w.Body) + _, err := buf.ReadFrom(w.Body) + assert.NoError(t, err) jsonStringBody := buf.String() assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody) } @@ -1220,11 +1224,11 @@ func TestContextError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) - c.Error(errors.New("first error")) + c.Error(errors.New("first error")) // nolint: errcheck assert.Len(t, c.Errors, 1) assert.Equal(t, "Error #01: first error\n", c.Errors.String()) - c.Error(&Error{ + c.Error(&Error{ // nolint: errcheck Err: errors.New("second error"), Meta: "some data 2", Type: ErrorTypePublic, @@ -1246,13 +1250,13 @@ func TestContextError(t *testing.T) { t.Error("didn't panic") } }() - c.Error(nil) + c.Error(nil) // nolint: errcheck } func TestContextTypedError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) - c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) + c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck + c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck for _, err := range c.Errors.ByType(ErrorTypePublic) { assert.Equal(t, ErrorTypePublic, err.Type) @@ -1267,7 +1271,7 @@ func TestContextAbortWithError(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") + c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, abortIndex, c.index) @@ -1713,7 +1717,8 @@ func TestContextStream(t *testing.T) { stopStream = false }() - w.Write([]byte("test")) + _, err := w.Write([]byte("test")) + assert.NoError(t, err) return stopStream }) @@ -1730,7 +1735,8 @@ func TestContextStreamWithClientGone(t *testing.T) { w.closeClient() }() - writer.Write([]byte("test")) + _, err := writer.Write([]byte("test")) + assert.NoError(t, err) return true }) diff --git a/debug_test.go b/debug_test.go index 97ff166..b3485d7 100644 --- a/debug_test.go +++ b/debug_test.go @@ -32,7 +32,7 @@ func TestIsDebugging(t *testing.T) { } func TestDebugPrint(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) SetMode(ReleaseMode) debugPrint("DEBUG this!") @@ -46,7 +46,7 @@ func TestDebugPrint(t *testing.T) { } func TestDebugPrintError(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintError(nil) debugPrintError(errors.New("this is an error")) @@ -56,7 +56,7 @@ func TestDebugPrintError(t *testing.T) { } func TestDebugPrintRoutes(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) SetMode(TestMode) @@ -65,7 +65,7 @@ func TestDebugPrintRoutes(t *testing.T) { } func TestDebugPrintLoadTemplate(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) debugPrintLoadTemplate(templ) @@ -75,7 +75,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { } func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGSetHTMLTemplate() SetMode(TestMode) @@ -84,7 +84,7 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { } func TestDebugPrintWARNINGDefault(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGDefault() SetMode(TestMode) @@ -98,7 +98,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { } func TestDebugPrintWARNINGNew(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGNew() SetMode(TestMode) @@ -106,7 +106,7 @@ func TestDebugPrintWARNINGNew(t *testing.T) { assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re) } -func captureOutput(f func()) string { +func captureOutput(t *testing.T, f func()) string { reader, writer, err := os.Pipe() if err != nil { panic(err) @@ -127,7 +127,8 @@ func captureOutput(f func()) string { go func() { var buf bytes.Buffer wg.Done() - io.Copy(&buf, reader) + _, err := io.Copy(&buf, reader) + assert.NoError(t, err) out <- buf.String() }() wg.Wait() diff --git a/errors_test.go b/errors_test.go index 9351b57..6aae1c1 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,7 +34,7 @@ func TestError(t *testing.T) { jsonBytes, _ := json.Marshal(err) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) - err.SetMeta(H{ + err.SetMeta(H{ // nolint: errcheck "status": "200", "data": "some data", }) @@ -44,7 +44,7 @@ func TestError(t *testing.T) { "data": "some data", }, err.JSON()) - err.SetMeta(H{ + err.SetMeta(H{ // nolint: errcheck "error": "custom error", "status": "200", "data": "some data", @@ -59,7 +59,7 @@ func TestError(t *testing.T) { status string data string } - err.SetMeta(customError{status: "200", data: "other data"}) + err.SetMeta(customError{status: "200", data: "other data"}) // nolint: errcheck assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON()) } diff --git a/gin.go b/gin.go index b7c77e1..cd47200 100644 --- a/gin.go +++ b/gin.go @@ -422,7 +422,10 @@ func serveError(c *Context, code int, defaultMessage []byte) { } if c.writermem.Status() == code { c.writermem.Header()["Content-Type"] = mimePlain - c.Writer.Write(defaultMessage) + _, err := c.Writer.Write(defaultMessage) + if err != nil { + debugPrint("cannot write message to writer during serve error: %v", err) + } return } c.writermem.WriteHeaderNow() diff --git a/gin_integration_test.go b/gin_integration_test.go index 01d5cf5..3ce6d80 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -87,7 +87,7 @@ func TestRunEmptyWithEnv(t *testing.T) { func TestRunTooMuchParams(t *testing.T) { router := New() assert.Panics(t, func() { - router.Run("2", "2") + assert.NoError(t, router.Run("2", "2")) }) } diff --git a/githubapi_test.go b/githubapi_test.go index 5253425..50faca0 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -338,7 +338,7 @@ func TestBindUriError(t *testing.T) { } router.Handle("GET", "/new/rest/:num", func(c *Context) { var m Member - c.BindUri(&m) + assert.Error(t, c.BindUri(&m)) }) path1, _ := exampleFromPath("/new/rest/:num") diff --git a/logger_test.go b/logger_test.go index 350599d..d016925 100644 --- a/logger_test.go +++ b/logger_test.go @@ -278,13 +278,13 @@ func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) router.GET("/error", func(c *Context) { - c.Error(errors.New("this is an error")) + c.Error(errors.New("this is an error")) // nolint: errcheck }) router.GET("/abort", func(c *Context) { - c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) + c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) // nolint: errcheck }) router.GET("/print", func(c *Context) { - c.Error(errors.New("this is an error")) + c.Error(errors.New("this is an error")) // nolint: errcheck c.String(http.StatusInternalServerError, "hola!") }) diff --git a/middleware_test.go b/middleware_test.go index 983ad93..fca1c53 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -208,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { router := New() router.Use(func(context *Context) { signature += "A" - context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) + context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) // nolint: errcheck }) router.Use(func(context *Context) { signature += "B" diff --git a/recovery.go b/recovery.go index f06ad56..0e35968 100644 --- a/recovery.go +++ b/recovery.go @@ -66,7 +66,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { // If the connection is dead, we can't write a status to it. if brokenPipe { - c.Error(err.(error)) + c.Error(err.(error)) // nolint: errcheck c.Abort() } else { c.AbortWithStatus(http.StatusInternalServerError) diff --git a/render/json.go b/render/json.go index 32d0fc4..c7cf330 100644 --- a/render/json.go +++ b/render/json.go @@ -67,8 +67,8 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { if err != nil { return err } - w.Write(jsonBytes) - return nil + _, err = w.Write(jsonBytes) + return err } // Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. @@ -78,8 +78,8 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { if err != nil { return err } - w.Write(jsonBytes) - return nil + _, err = w.Write(jsonBytes) + return err } // WriteContentType (IndentedJSON) writes JSON ContentType. @@ -96,10 +96,13 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { } // if the jsonBytes is array values if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) { - w.Write([]byte(r.Prefix)) + _, err = w.Write([]byte(r.Prefix)) + if err != nil { + return err + } } - w.Write(jsonBytes) - return nil + _, err = w.Write(jsonBytes) + return err } // WriteContentType (SecureJSON) writes JSON ContentType. @@ -116,15 +119,27 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { } if r.Callback == "" { - w.Write(ret) - return nil + _, err = w.Write(ret) + return err } callback := template.JSEscapeString(r.Callback) - w.Write([]byte(callback)) - w.Write([]byte("(")) - w.Write(ret) - w.Write([]byte(")")) + _, err = w.Write([]byte(callback)) + if err != nil { + return err + } + _, err = w.Write([]byte("(")) + if err != nil { + return err + } + _, err = w.Write(ret) + if err != nil { + return err + } + _, err = w.Write([]byte(")")) + if err != nil { + return err + } return nil } @@ -151,8 +166,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { buffer.WriteString(cvt) } - w.Write(buffer.Bytes()) - return nil + _, err = w.Write(buffer.Bytes()) + return err } // WriteContentType (AsciiJSON) writes JSON ContentType. diff --git a/render/protobuf.go b/render/protobuf.go index 4789507..15aca99 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -26,8 +26,8 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error { return err } - w.Write(bytes) - return nil + _, err = w.Write(bytes) + return err } // WriteContentType (ProtoBuf) writes ProtoBuf ContentType. diff --git a/render/render_test.go b/render/render_test.go index 4c9b180..3df04a1 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -71,7 +71,7 @@ func TestRenderJSONPanics(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - assert.Panics(t, func() { (JSON{data}).Render(w) }) + assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) }) } func TestRenderIndentedJSON(t *testing.T) { @@ -335,7 +335,7 @@ func TestRenderRedirect(t *testing.T) { } w = httptest.NewRecorder() - assert.Panics(t, func() { data2.Render(w) }) + assert.Panics(t, func() { assert.NoError(t, data2.Render(w)) }) // only improve coverage data2.WriteContentType(w) diff --git a/render/text.go b/render/text.go index 2ea7343..4e52d4c 100644 --- a/render/text.go +++ b/render/text.go @@ -20,8 +20,7 @@ var plainContentType = []string{"text/plain; charset=utf-8"} // Render (String) writes data with custom ContentType. func (r String) Render(w http.ResponseWriter) error { - WriteString(w, r.Format, r.Data) - return nil + return WriteString(w, r.Format, r.Data) } // WriteContentType (String) writes Plain ContentType. @@ -30,11 +29,12 @@ func (r String) WriteContentType(w http.ResponseWriter) { } // WriteString writes data according to its format and write custom ContentType. -func WriteString(w http.ResponseWriter, format string, data []interface{}) { +func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) { writeContentType(w, plainContentType) if len(data) > 0 { - fmt.Fprintf(w, format, data...) + _, err = fmt.Fprintf(w, format, data...) return } - io.WriteString(w, format) + _, err = io.WriteString(w, format) + return } diff --git a/render/yaml.go b/render/yaml.go index 33bc325..0df7836 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -26,8 +26,8 @@ func (r YAML) Render(w http.ResponseWriter) error { return err } - w.Write(bytes) - return nil + _, err = w.Write(bytes) + return err } // WriteContentType (YAML) writes YAML ContentType for response. diff --git a/response_writer_test.go b/response_writer_test.go index cc5a89d..a5e111e 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -103,7 +103,8 @@ func TestResponseWriterHijack(t *testing.T) { w := ResponseWriter(writer) assert.Panics(t, func() { - w.Hijack() + _, _, err := w.Hijack() + assert.NoError(t, err) }) assert.True(t, w.Written()) diff --git a/routes_test.go b/routes_test.go index c4d5972..af4caf7 100644 --- a/routes_test.go +++ b/routes_test.go @@ -251,7 +251,8 @@ func TestRouteStaticFile(t *testing.T) { t.Error(err) } defer os.Remove(f.Name()) - f.WriteString("Gin Web Framework") + _, err = f.WriteString("Gin Web Framework") + assert.NoError(t, err) f.Close() dir, filename := filepath.Split(f.Name()) From 4867ff9634d1889156587d5add70d2b29c447542 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 18 Jan 2019 04:57:06 +0300 Subject: [PATCH 291/912] fix Context.Next() - recheck len of handlers every iteration (#1745) * fix Context.Next() - recheck len of handlers every iteration * add tests when Context.reset() can be called inside of handler TestEngineHandleContext TestContextResetInHandler TestRouterStaticFSFileNotFound * Context.Next() - format to while style --- context.go | 3 ++- context_test.go | 12 ++++++++++++ gin_test.go | 17 +++++++++++++++++ routes_test.go | 10 ++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 4dc94ea..26badfc 100644 --- a/context.go +++ b/context.go @@ -105,8 +105,9 @@ func (c *Context) Handler() HandlerFunc { // See example in GitHub. func (c *Context) Next() { c.index++ - for s := int8(len(c.handlers)); c.index < s; c.index++ { + for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) + c.index++ } } diff --git a/context_test.go b/context_test.go index 29ec1a2..ea936b8 100644 --- a/context_test.go +++ b/context_test.go @@ -1743,3 +1743,15 @@ func TestContextStreamWithClientGone(t *testing.T) { assert.Equal(t, "test", w.Body.String()) } + +func TestContextResetInHandler(t *testing.T) { + w := CreateTestResponseRecorder() + c, _ := CreateTestContext(w) + + c.handlers = []HandlerFunc{ + func(c *Context) { c.reset() }, + } + assert.NotPanics(t, func() { + c.Next() + }) +} diff --git a/gin_test.go b/gin_test.go index 353c9be..e013df0 100644 --- a/gin_test.go +++ b/gin_test.go @@ -471,6 +471,23 @@ func TestListOfRoutes(t *testing.T) { }) } +func TestEngineHandleContext(t *testing.T) { + r := New() + r.GET("/", func(c *Context) { + c.Request.URL.Path = "/v2" + r.HandleContext(c) + }) + v2 := r.Group("/v2") + { + v2.GET("/", func(c *Context) {}) + } + + assert.NotPanics(t, func() { + w := performRequest(r, "GET", "/") + assert.Equal(t, 301, w.Code) + }) +} + func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { for _, gotRoute := range gotRoutes { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { diff --git a/routes_test.go b/routes_test.go index af4caf7..8d50292 100644 --- a/routes_test.go +++ b/routes_test.go @@ -427,6 +427,16 @@ func TestRouterStaticFSNotFound(t *testing.T) { assert.Equal(t, "non existent", w.Body.String()) } +func TestRouterStaticFSFileNotFound(t *testing.T) { + router := New() + + router.StaticFS("/", http.FileSystem(http.Dir("."))) + + assert.NotPanics(t, func() { + performRequest(router, "GET", "/nonexistent") + }) +} + func TestRouteRawPath(t *testing.T) { route := New() route.UseRawPath = true From b4f51559825a055dcec93fa282c20018d073aaaa Mon Sep 17 00:00:00 2001 From: Sai Date: Sun, 20 Jan 2019 09:39:09 +0900 Subject: [PATCH 292/912] Fix not to pass formatted string to Fprintf's format specifier parameter (#1747) --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index a55d26e..d2869f5 100644 --- a/logger.go +++ b/logger.go @@ -191,7 +191,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { param.Path = path - fmt.Fprintf(out, formatter(param)) + fmt.Fprint(out, formatter(param)) } } } From f38a3fe65f102dd765e097166e6a41f9e99777f6 Mon Sep 17 00:00:00 2001 From: Ryan <46182144+ryanker@users.noreply.github.com> Date: Sun, 20 Jan 2019 18:27:04 +0800 Subject: [PATCH 293/912] fix password error (#1728) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2dc9e5f..0dcaa8e 100644 --- a/README.md +++ b/README.md @@ -620,7 +620,7 @@ func main() { // // // user - // 123 + // 123 // ) router.POST("/loginXML", func(c *gin.Context) { var xml Login From d27685e714cb0b9375e0c9d3ca3df5a6a4945a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 26 Jan 2019 02:28:39 +0800 Subject: [PATCH 294/912] chore: attempt to fix some gomod issue (#1751) #1604 #1566 #1700 #1737 because some dependencies only are used on example i.e. grpc. Or migrate `examples` to gin-gonic/examples`? --- go.mod | 39 +++++++++++++++--------------- go.sum | 64 ++++++++++++++++++-------------------------------- tools/tools.go | 25 -------------------- 3 files changed, 43 insertions(+), 85 deletions(-) delete mode 100644 tools/tools.go diff --git a/go.mod b/go.mod index ef4103f..54573a8 100644 --- a/go.mod +++ b/go.mod @@ -1,30 +1,31 @@ module github.com/gin-gonic/gin require ( - github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 - github.com/client9/misspell v0.3.4 - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 - github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 - github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b + github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482 github.com/golang/protobuf v1.2.0 - github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 github.com/json-iterator/go v1.1.5 - github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 github.com/mattn/go-isatty v0.0.4 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.2.2 - github.com/thinkerou/favicon v0.1.0 - github.com/ugorji/go v1.1.1 - golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e - golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd - golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f - golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba // indirect - google.golang.org/grpc v1.15.0 + github.com/stretchr/testify v1.3.0 + github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 + golang.org/x/net v0.0.0-20190119204137-ed066c81e75e + golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 + golang.org/x/sys v0.0.0-20190124100055-b90733256f2e // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 - gopkg.in/yaml.v2 v2.2.1 + gopkg.in/yaml.v2 v2.2.2 +) + +exclude ( + github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160 + github.com/client9/misspell v0.3.4 + github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 + github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768 + github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 + github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 + github.com/thinkerou/favicon v0.1.0 + golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b + golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 + google.golang.org/grpc v1.18.0 ) diff --git a/go.sum b/go.sum index 2ef7f13..95e2b4f 100644 --- a/go.sum +++ b/go.sum @@ -1,72 +1,54 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 h1:tRsilif6pbtt+PX6uRoyGd+qR+4ZPucFZLHlc3Ak6z8= -github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296/go.mod h1:/dBk8ICkslPCmyRdn4azP+QvBxL6Eg3EYxUGI9xMMFw= +github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160 h1:HJpuhXOHC4EkXDARsLjmXAV9FhlY6qFDnKI/MJM6eoE= +github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 h1:QnnoVdChKs+GeTvN4rPYTW6b5U6M3HMEvQ/+x4IGtfY= -github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66/go.mod h1:kTEh6M2J/mh7nsskr28alwLCXm/DSG5OSA/o31yy2XU= -github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= -github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8Jc6zMvyRz3PCQrTTCXnVRvEzyBcM890= -github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482 h1:iOz5sIQUvuOlpiC7Q6+MmJQpWnlneYX98QIGf+2m50Y= +github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 h1:cW/amwGEJK5MSKntPXRjX4dxs/nGxGT8gXKIsKFmHGc= -github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15/go.mod h1:Fdm/oWRW+CH8PRbLntksCNtmcCBximKPkVQYvmMl80k= -github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= -github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/thinkerou/favicon v0.1.0 h1:eWMISKTpHq2G8HOuKn7ydD55j5DDehx94b0C2y8ABMs= -github.com/thinkerou/favicon v0.1.0/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= -github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= -github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= -golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd h1:cgsAvzdqkDKdI02tIvDjO225vDPHMDCgfKqx5KEVI7U= -golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY+LsWmuwob+CRS1BmdRdjphAm9mH4= +github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q= +golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc= -golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190124100055-b90733256f2e h1:3GIlrlVLfkoipSReOMNAgApI0ajnalyLa/EZHHca/XI= +golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw= -google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/tools/tools.go b/tools/tools.go deleted file mode 100644 index 7113e71..0000000 --- a/tools/tools.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build tools - -// This package exists to cause `go mod` and `go get` to believe these tools -// are dependencies, even though they are not runtime dependencies of any -// gin package. This means they will appear in `go.mod` file, but will not -// be a part of the build. - -package tools - -import ( - _ "github.com/campoy/embedmd" - _ "github.com/client9/misspell/cmd/misspell" - _ "github.com/dustin/go-broadcast" - _ "github.com/gin-gonic/autotls" - _ "github.com/jessevdk/go-assets" - _ "github.com/manucorporat/stats" - _ "github.com/thinkerou/favicon" - _ "golang.org/x/crypto/acme/autocert" - _ "golang.org/x/lint/golint" - _ "google.golang.org/grpc" -) From 5acf6601170bb49a1958c055d66d54ba152dc34b Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Mon, 4 Feb 2019 04:27:00 +0300 Subject: [PATCH 295/912] fix travis freeze on concurrent test (#1761) --- gin_integration_test.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index 3ce6d80..b80cbb2 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -188,15 +188,12 @@ func TestConcurrentHandleContext(t *testing.T) { }) router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - ts := httptest.NewServer(router) - defer ts.Close() - var wg sync.WaitGroup iterations := 200 wg.Add(iterations) for i := 0; i < iterations; i++ { go func() { - testRequest(t, ts.URL+"/") + testGetRequestHandler(t, router, "/") wg.Done() }() } @@ -217,3 +214,14 @@ func TestConcurrentHandleContext(t *testing.T) { // testRequest(t, "http://localhost:8033/example") // } + +func testGetRequestHandler(t *testing.T, h http.Handler, url string) { + req, err := http.NewRequest("GET", url, nil) + assert.NoError(t, err) + + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + + assert.Equal(t, "it worked", w.Body.String(), "resp body should match") + assert.Equal(t, 200, w.Code, "should get a 200") +} From a768f064d53c8010d902533927441be13b1bfe17 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Mon, 18 Feb 2019 04:35:08 +0300 Subject: [PATCH 296/912] fix many redirects (#1760) (#1764) * fix many redirects (#1760) * fix @thinkerou review --- gin.go | 3 +++ gin_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/gin.go b/gin.go index cd47200..6e5ea6d 100644 --- a/gin.go +++ b/gin.go @@ -355,8 +355,11 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // This can be done by setting c.Request.URL.Path to your new target. // Disclaimer: You can loop yourself to death with this, use wisely. func (engine *Engine) HandleContext(c *Context) { + oldIndexValue := c.index c.reset() engine.handleHTTPRequest(c) + + c.index = oldIndexValue } func (engine *Engine) handleHTTPRequest(c *Context) { diff --git a/gin_test.go b/gin_test.go index e013df0..f9a1c6f 100644 --- a/gin_test.go +++ b/gin_test.go @@ -12,6 +12,8 @@ import ( "net/http" "net/http/httptest" "reflect" + "strconv" + "sync/atomic" "testing" "time" @@ -488,6 +490,43 @@ func TestEngineHandleContext(t *testing.T) { }) } +func TestEngineHandleContextManyReEntries(t *testing.T) { + expectValue := 10000 + + var handlerCounter, middlewareCounter int64 + + r := New() + r.Use(func(c *Context) { + atomic.AddInt64(&middlewareCounter, 1) + }) + r.GET("/:count", func(c *Context) { + countStr := c.Param("count") + count, err := strconv.Atoi(countStr) + assert.NoError(t, err) + + n, err := c.Writer.Write([]byte(".")) + assert.NoError(t, err) + assert.Equal(t, 1, n) + + switch { + case count > 0: + c.Request.URL.Path = "/" + strconv.Itoa(count-1) + r.HandleContext(c) + } + }, func(c *Context) { + atomic.AddInt64(&handlerCounter, 1) + }) + + assert.NotPanics(t, func() { + w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value + assert.Equal(t, 200, w.Code) + assert.Equal(t, expectValue, w.Body.Len()) + }) + + assert.Equal(t, int64(expectValue), handlerCounter) + assert.Equal(t, int64(expectValue), middlewareCounter) +} + func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { for _, gotRoute := range gotRoutes { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { From 31bbb10f34e4673815ab66099571bac95cf4113d Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Mon, 18 Feb 2019 05:10:45 +0300 Subject: [PATCH 297/912] Make silent debug info on tests (#1765) * make silent log on tests * fix coverage: check end-of-line at the end of debug msg --- debug_test.go | 2 +- deprecated_test.go | 4 +++- gin_test.go | 29 +++++++++++++++++------------ githubapi_test.go | 10 +++++----- recovery_test.go | 1 + 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/debug_test.go b/debug_test.go index b3485d7..d338f0a 100644 --- a/debug_test.go +++ b/debug_test.go @@ -39,7 +39,7 @@ func TestDebugPrint(t *testing.T) { SetMode(TestMode) debugPrint("DEBUG this!") SetMode(DebugMode) - debugPrint("these are %d %s\n", 2, "error messages") + debugPrint("these are %d %s", 2, "error messages") SetMode(TestMode) }) assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re) diff --git a/deprecated_test.go b/deprecated_test.go index 7a875fe..f8df651 100644 --- a/deprecated_test.go +++ b/deprecated_test.go @@ -24,7 +24,9 @@ func TestBindWith(t *testing.T) { Foo string `form:"foo"` Bar string `form:"bar"` } - assert.NoError(t, c.BindWith(&obj, binding.Form)) + captureOutput(t, func() { + assert.NoError(t, c.BindWith(&obj, binding.Form)) + }) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) diff --git a/gin_test.go b/gin_test.go index f9a1c6f..11bdd79 100644 --- a/gin_test.go +++ b/gin_test.go @@ -27,18 +27,23 @@ func formatAsDate(t time.Time) string { func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server { SetMode(mode) - router := New() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - loadMethod(router) - router.GET("/test", func(c *Context) { - c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) - }) - router.GET("/raw", func(c *Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + defer SetMode(TestMode) + + var router *Engine + captureOutput(t, func() { + router = New() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + loadMethod(router) + router.GET("/test", func(c *Context) { + c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) + }) + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) }) }) diff --git a/githubapi_test.go b/githubapi_test.go index 50faca0..29aa158 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -287,7 +287,7 @@ var githubAPI = []route{ func TestShouldBindUri(t *testing.T) { DefaultWriter = os.Stdout - router := Default() + router := New() type Person struct { Name string `uri:"name" binding:"required"` @@ -309,7 +309,7 @@ func TestShouldBindUri(t *testing.T) { func TestBindUri(t *testing.T) { DefaultWriter = os.Stdout - router := Default() + router := New() type Person struct { Name string `uri:"name" binding:"required"` @@ -331,7 +331,7 @@ func TestBindUri(t *testing.T) { func TestBindUriError(t *testing.T) { DefaultWriter = os.Stdout - router := Default() + router := New() type Member struct { Number string `uri:"num" binding:"required,uuid"` @@ -361,7 +361,7 @@ func githubConfigRouter(router *Engine) { func TestGithubAPI(t *testing.T) { DefaultWriter = os.Stdout - router := Default() + router := New() githubConfigRouter(router) for _, route := range githubAPI { @@ -436,7 +436,7 @@ func BenchmarkParallelGithub(b *testing.B) { func BenchmarkParallelGithubDefault(b *testing.B) { DefaultWriter = os.Stdout - router := Default() + router := New() githubConfigRouter(router) req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil) diff --git a/recovery_test.go b/recovery_test.go index e886eaa..0a6d627 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -43,6 +43,7 @@ func TestPanicInHandler(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") + SetMode(TestMode) } // TestPanicWithAbort assert that panic has been recovered even if context.Abort was used. From 5846ceba8b1074c4555491ef4df2a54e75442358 Mon Sep 17 00:00:00 2001 From: awkj Date: Wed, 20 Feb 2019 00:02:37 +0800 Subject: [PATCH 298/912] add notify accept signal (#1740) * add notify accept signal * add import * update readme,keep same with example --- README.md | 6 +++++- examples/graceful-shutdown/graceful-shutdown/server.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0dcaa8e..3da8785 100644 --- a/README.md +++ b/README.md @@ -1633,6 +1633,7 @@ import ( "net/http" "os" "os/signal" + "syscall" "time" "github.com/gin-gonic/gin" @@ -1660,7 +1661,10 @@ func main() { // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal) - signal.Notify(quit, os.Interrupt) + // kill (no param) default send syscanll.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutdown Server ...") diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go index af4f214..33be0c8 100644 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "os/signal" + "syscall" "time" "github.com/gin-gonic/gin" @@ -35,7 +36,10 @@ func main() { // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal) - signal.Notify(quit, os.Interrupt) + // kill (no param) default send syscanll.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutdown Server ...") From fece76d93fd65f795bea75f59fb5e3e03320dc6d Mon Sep 17 00:00:00 2001 From: Jeremy Loy Date: Tue, 19 Feb 2019 21:41:46 -0500 Subject: [PATCH 299/912] Add NewRelic middleware example. (#1526) * Add NewRelic middleware example. * Update go.mod * Update main.go --- examples/new_relic/README.md | 30 ++++++++++++++++++++++++++ examples/new_relic/main.go | 42 ++++++++++++++++++++++++++++++++++++ go.mod | 1 + 3 files changed, 73 insertions(+) create mode 100644 examples/new_relic/README.md create mode 100644 examples/new_relic/main.go diff --git a/examples/new_relic/README.md b/examples/new_relic/README.md new file mode 100644 index 0000000..70f1494 --- /dev/null +++ b/examples/new_relic/README.md @@ -0,0 +1,30 @@ +The [New Relic Go Agent](https://github.com/newrelic/go-agent) provides a nice middleware for the stdlib handler signature. +The following is an adaptation of that middleware for Gin. + +```golang +const ( + // NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context + NewRelicTxnKey = "NewRelicTxnKey" +) + +// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler +func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc { + return func(ctx *gin.Context) { + txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request) + defer txn.End() + ctx.Set(NewRelicTxnKey, txn) + ctx.Next() + } +} +``` +and in `main.go` or equivalent... +```golang +router := gin.Default() +cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY")) +app, err := newrelic.NewApplication(cfg) +if err != nil { + log.Printf("failed to make new_relic app: %v", err) +} else { + router.Use(adapters.NewRelicMonitoring(app)) +} + ``` diff --git a/examples/new_relic/main.go b/examples/new_relic/main.go new file mode 100644 index 0000000..f85f783 --- /dev/null +++ b/examples/new_relic/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "log" + "net/http" + "os" + + "github.com/gin-gonic/gin" + "github.com/newrelic/go-agent" +) + +const ( + // NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context + NewRelicTxnKey = "NewRelicTxnKey" +) + +// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler +func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc { + return func(ctx *gin.Context) { + txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request) + defer txn.End() + ctx.Set(NewRelicTxnKey, txn) + ctx.Next() + } +} + +func main() { + router := gin.Default() + + cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY")) + app, err := newrelic.NewApplication(cfg) + if err != nil { + log.Printf("failed to make new_relic app: %v", err) + } else { + router.Use(NewRelicMonitoring(app)) + } + + router.GET("/", func(c *gin.Context) { + c.String(http.StatusOK, "Hello World!\n") + }) + router.Run() +} diff --git a/go.mod b/go.mod index 54573a8..6f9d68d 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ exclude ( github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768 github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 + github.com/newrelic/go-agent v2.5.0+incompatible github.com/thinkerou/favicon v0.1.0 golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 From 90587c7787a37e3b32375627717b936a17ab8e94 Mon Sep 17 00:00:00 2001 From: ffhelicopter <32922889+ffhelicopter@users.noreply.github.com> Date: Wed, 20 Feb 2019 13:24:29 +0800 Subject: [PATCH 300/912] Update: examples/graceful-shutdown/server.go (#1530) * Update server.go It's necessary that catching ctx.Done() * Update server.go * Update server.go * Update README.md * Update README.md --- README.md | 5 +++++ examples/graceful-shutdown/graceful-shutdown/server.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 3da8785..90f6e1d 100644 --- a/README.md +++ b/README.md @@ -1673,6 +1673,11 @@ func main() { if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } + // catching ctx.Done(). timeout of 5 seconds. + select { + case <-ctx.Done(): + log.Println("timeout of 5 seconds.") + } log.Println("Server exiting") } ``` diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go index 33be0c8..999a209 100644 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -48,5 +48,10 @@ func main() { if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } + // catching ctx.Done(). timeout of 5 seconds. + select { + case <-ctx.Done(): + log.Println("timeout of 5 seconds.") + } log.Println("Server exiting") } From a58a2f9bf368a7976d26dba4c16c90fad4c1db6e Mon Sep 17 00:00:00 2001 From: Olivier Robardet Date: Wed, 20 Feb 2019 14:14:16 +0100 Subject: [PATCH 301/912] Add a function to force color in console output (#1724) Add a function `ForceConsoleColor`, like `DisableConsoleColor` but to force coloring the output. It usefull when some IDE's integrated console (like IntelliJ or Goland) are not detected as TTY, but can display colors. Also helps if one want to output color in log file (#1590) and as a workaround for #1547. --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++--- logger.go | 10 ++++++++-- logger_test.go | 7 +++++++ 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 90f6e1d..058eee2 100644 --- a/README.md +++ b/README.md @@ -215,9 +215,6 @@ $ go build -tags=jsoniter . ```go func main() { - // Disable Console Color - // gin.DisableConsoleColor() - // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default() @@ -570,6 +567,48 @@ func main() { ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " ``` +### Controlling Log output coloring + +By default, logs output on console should be colorized depending on the detected TTY. + +Never colorize logs: + +```go +func main() { + // Disable log's color + gin.DisableConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +Always colorize logs: + +```go +func main() { + // Force log's color + gin.ForceConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + ### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). diff --git a/logger.go b/logger.go index d2869f5..6d8f838 100644 --- a/logger.go +++ b/logger.go @@ -24,6 +24,7 @@ var ( cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) reset = string([]byte{27, 91, 48, 109}) disableColor = false + forceColor = false ) // LoggerConfig defines the config for Logger middleware. @@ -90,6 +91,11 @@ func DisableConsoleColor() { disableColor = true } +// ForceConsoleColor force color output in the console. +func ForceConsoleColor() { + forceColor = true +} + // ErrorLogger returns a handlerfunc for any error type. func ErrorLogger() HandlerFunc { return ErrorLoggerT(ErrorTypeAny) @@ -144,9 +150,9 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { isTerm := true - if w, ok := out.(*os.File); !ok || + if w, ok := out.(*os.File); (!ok || (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) || - disableColor { + disableColor) && !forceColor { isTerm = false } diff --git a/logger_test.go b/logger_test.go index d016925..8770b5f 100644 --- a/logger_test.go +++ b/logger_test.go @@ -340,3 +340,10 @@ func TestDisableConsoleColor(t *testing.T) { DisableConsoleColor() assert.True(t, disableColor) } + +func TestForceConsoleColor(t *testing.T) { + New() + assert.False(t, forceColor) + ForceConsoleColor() + assert.True(t, forceColor) +} From e6886e1539d89365f9970e1b5d9af1a2ccea16d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 21 Feb 2019 20:32:55 +0800 Subject: [PATCH 302/912] chore: fix Make script when failed (#1774) --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7211144..7ab57e2 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,10 @@ test: exit 1; \ elif grep -q "build failed" tmp.out; then \ rm tmp.out; \ - exit; \ + exit 1; \ + elif grep -q "setup failed" tmp.out; then \ + rm tmp.out; \ + exit 1; \ fi; \ if [ -f profile.out ]; then \ cat profile.out | grep -v "mode:" >> coverage.out; \ From 4e86b17e734074af0317f9b0c40167f45fd0ca91 Mon Sep 17 00:00:00 2001 From: Mara Kim Date: Thu, 21 Feb 2019 22:45:32 -0500 Subject: [PATCH 303/912] Set socket to recieve writes (#1134) * Set socket to recieve writes * Update gin.go --- gin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gin.go b/gin.go index 6e5ea6d..ad64c35 100644 --- a/gin.go +++ b/gin.go @@ -318,6 +318,7 @@ func (engine *Engine) RunUnix(file string) (err error) { return } defer listener.Close() + os.Chmod(file, 0777) err = http.Serve(listener, engine) return } From 48f6c6137c9e326dc0aea110a43ad5a7a590073e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bazaglia?= Date: Fri, 22 Feb 2019 01:23:52 -0300 Subject: [PATCH 304/912] allow ignoring field on form mapping (#1733) --- binding/binding_test.go | 25 +++++++++++++++++++++++++ binding/form_mapping.go | 3 +++ 2 files changed, 28 insertions(+) diff --git a/binding/binding_test.go b/binding/binding_test.go index 1044e6c..c9dea34 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -61,6 +61,10 @@ type FooStructForMapType struct { MapFoo map[string]interface{} `form:"map_foo"` } +type FooStructForIgnoreFormTag struct { + Foo *string `form:"-"` +} + type InvalidNameType struct { TestName string `invalid_name:"test_name"` } @@ -278,6 +282,12 @@ func TestBindingFormForTime2(t *testing.T) { "", "") } +func TestFormBindingIgnoreField(t *testing.T) { + testFormBindingIgnoreField(t, "POST", + "/", "/", + "-=bar", "") +} + func TestBindingFormInvalidName(t *testing.T) { testFormBindingInvalidName(t, "POST", "/", "/", @@ -860,6 +870,21 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod assert.Error(t, err) } +func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, "form", b.Name()) + + obj := FooStructForIgnoreFormTag{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + + assert.Nil(t, obj.Foo) +} + func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 8900ab7..8eb5c0d 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -41,6 +41,9 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { defaultValue = defaultList[1] } } + if inputFieldName == "-" { + continue + } if inputFieldName == "" { inputFieldName = typeField.Name From d7daffc26b212514adb756e1ba807a6b8242dd37 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 22 Feb 2019 12:53:47 +0800 Subject: [PATCH 305/912] Use camel case instead of ALL_CAPS (#1419) * Use camel case instead of ALL_CAPS * Update mode.go --- mode.go | 6 +++--- mode_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mode.go b/mode.go index f787b5c..8aa84aa 100644 --- a/mode.go +++ b/mode.go @@ -11,8 +11,8 @@ import ( "github.com/gin-gonic/gin/binding" ) -// ENV_GIN_MODE indicates environment name for gin mode. -const ENV_GIN_MODE = "GIN_MODE" +// EnvGinMode indicates environment name for gin mode. +const EnvGinMode = "GIN_MODE" const ( // DebugMode indicates gin mode is debug. @@ -44,7 +44,7 @@ var ginMode = debugCode var modeName = DebugMode func init() { - mode := os.Getenv(ENV_GIN_MODE) + mode := os.Getenv(EnvGinMode) SetMode(mode) } diff --git a/mode_test.go b/mode_test.go index cf27acd..3dba515 100644 --- a/mode_test.go +++ b/mode_test.go @@ -13,13 +13,13 @@ import ( ) func init() { - os.Setenv(ENV_GIN_MODE, TestMode) + os.Setenv(EnvGinMode, TestMode) } func TestSetMode(t *testing.T) { assert.Equal(t, testCode, ginMode) assert.Equal(t, TestMode, Mode()) - os.Unsetenv(ENV_GIN_MODE) + os.Unsetenv(EnvGinMode) SetMode("") assert.Equal(t, debugCode, ginMode) From 184661cfa2f5ad6000c9893a0ffba8da8c1bd2ec Mon Sep 17 00:00:00 2001 From: Pierre Massat Date: Thu, 21 Feb 2019 21:12:05 -0800 Subject: [PATCH 306/912] Add response size to LogFormatterParams (#1752) --- logger.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/logger.go b/logger.go index 6d8f838..bd28a11 100644 --- a/logger.go +++ b/logger.go @@ -64,6 +64,8 @@ type LogFormatterParams struct { ErrorMessage string // IsTerm shows whether does gin's output descriptor refers to a terminal. IsTerm bool + // BodySize is the size of the Response Body + BodySize int } // defaultLogFormatter is the default log format function Logger middleware uses. @@ -191,6 +193,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { param.StatusCode = c.Writer.Status() param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() + param.BodySize = c.Writer.Size() + if raw != "" { path = path + "?" + raw } From 7b1081a73fe4559697f4fb5a44261fbdc1d130b2 Mon Sep 17 00:00:00 2001 From: songjiayang Date: Fri, 22 Feb 2019 14:20:24 +0800 Subject: [PATCH 307/912] issue_1721: fix render writeHeaders to make it the same as http.Header.Set (#1722) --- render/reader.go | 4 ++-- render/render_test.go | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/render/reader.go b/render/reader.go index ab60e53..312af74 100644 --- a/render/reader.go +++ b/render/reader.go @@ -36,8 +36,8 @@ func (r Reader) WriteContentType(w http.ResponseWriter) { func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { header := w.Header() for k, v := range headers { - if val := header[k]; len(val) == 0 { - header[k] = []string{v} + if header.Get(k) == "" { + header.Set(k, v) } } } diff --git a/render/render_test.go b/render/render_test.go index 3df04a1..76e29ee 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -470,6 +470,7 @@ func TestRenderReader(t *testing.T) { body := "#!PNG some raw data" headers := make(map[string]string) headers["Content-Disposition"] = `attachment; filename="filename.png"` + headers["x-request-id"] = "requestId" err := (Reader{ ContentLength: int64(len(body)), @@ -483,4 +484,5 @@ func TestRenderReader(t *testing.T) { assert.Equal(t, "image/png", w.Header().Get("Content-Type")) assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length")) assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) + assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } From e6288e90eb313122410bf1335d502a062c1ae610 Mon Sep 17 00:00:00 2001 From: Sai Date: Fri, 22 Feb 2019 17:48:55 +0900 Subject: [PATCH 308/912] Change color methods in using defaultLogger function to public (#1771) Fix https://github.com/gin-gonic/gin/issues/1768 --- logger.go | 85 ++++++++++++++++++++++++++++---------------------- logger_test.go | 19 +++++++++++ 2 files changed, 67 insertions(+), 37 deletions(-) diff --git a/logger.go b/logger.go index bd28a11..dc63997 100644 --- a/logger.go +++ b/logger.go @@ -68,13 +68,58 @@ type LogFormatterParams struct { BodySize int } +// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. +func (p *LogFormatterParams) StatusCodeColor() string { + code := p.StatusCode + + switch { + case code >= http.StatusOK && code < http.StatusMultipleChoices: + return green + case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: + return white + case code >= http.StatusBadRequest && code < http.StatusInternalServerError: + return yellow + default: + return red + } +} + +// MethodColor is the ANSI color for appropriately logging http method to a terminal. +func (p *LogFormatterParams) MethodColor() string { + method := p.Method + + switch method { + case "GET": + return blue + case "POST": + return cyan + case "PUT": + return yellow + case "DELETE": + return red + case "PATCH": + return green + case "HEAD": + return magenta + case "OPTIONS": + return white + default: + return reset + } +} + +// ResetColor resets all escape attributes. +func (p *LogFormatterParams) ResetColor() string { + return reset +} + // defaultLogFormatter is the default log format function Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { var statusColor, methodColor, resetColor string if param.IsTerm { - statusColor = colorForStatus(param.StatusCode) - methodColor = colorForMethod(param.Method) - resetColor = reset + statusColor = param.StatusCodeColor() + methodColor = param.MethodColor() + resetColor = param.ResetColor() } return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", @@ -205,37 +250,3 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { } } } - -func colorForStatus(code int) string { - switch { - case code >= http.StatusOK && code < http.StatusMultipleChoices: - return green - case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: - return white - case code >= http.StatusBadRequest && code < http.StatusInternalServerError: - return yellow - default: - return red - } -} - -func colorForMethod(method string) string { - switch method { - case "GET": - return blue - case "POST": - return cyan - case "PUT": - return yellow - case "DELETE": - return red - case "PATCH": - return green - case "HEAD": - return magenta - case "OPTIONS": - return white - default: - return reset - } -} diff --git a/logger_test.go b/logger_test.go index 8770b5f..c551677 100644 --- a/logger_test.go +++ b/logger_test.go @@ -257,6 +257,13 @@ func TestDefaultLogFormatter(t *testing.T) { } func TestColorForMethod(t *testing.T) { + colorForMethod := func(method string) string { + p := LogFormatterParams{ + Method: method, + } + return p.MethodColor() + } + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") @@ -268,12 +275,24 @@ func TestColorForMethod(t *testing.T) { } func TestColorForStatus(t *testing.T) { + colorForStatus := func(code int) string { + p := LogFormatterParams{ + StatusCode: code, + } + return p.StatusCodeColor() + } + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") } +func TestResetColor(t *testing.T) { + p := LogFormatterParams{} + assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor()) +} + func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) From d6adc8d0cc4e5a87e249923f511d093ffa552c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 26 Feb 2019 10:45:44 +0800 Subject: [PATCH 309/912] chore: add go1.12 support (#1780) * chore: add go1.12 support * Update .travis.yml --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2eeb0b3..be196fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ go: - 1.9.x - 1.10.x - 1.11.x + - 1.12.x - master matrix: @@ -14,6 +15,8 @@ matrix: include: - go: 1.11.x env: GO111MODULE=on + - go: 1.12.x + env: GO111MODULE=on git: depth: 10 From 62749f0db4aa5ee1045779b457fe1f28a553f467 Mon Sep 17 00:00:00 2001 From: Luis GG Date: Tue, 26 Feb 2019 01:15:40 -0300 Subject: [PATCH 310/912] Add context.HandlerNames() (#1729) * Add context.HandlerNames() This change adds a HandlerNames method that will return all registered handles in the context, in descending order This is useful for debugging and troubleshooting purposes, especially in large apps * Tests Add tests for HandlerNames * Fix HandlerNames test * Simplify test --- context.go | 10 ++++++++++ context_test.go | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/context.go b/context.go index 26badfc..fa63ec8 100644 --- a/context.go +++ b/context.go @@ -91,6 +91,16 @@ func (c *Context) HandlerName() string { return nameOfFunction(c.handlers.Last()) } +// HandlerNames returns a list of all registered handlers for this context in descending order, +// following the semantics of HandlerName() +func (c *Context) HandlerNames() []string { + hn := make([]string, 0, len(c.handlers)) + for _, val := range c.handlers { + hn = append(hn, nameOfFunction(val)) + } + return hn +} + // Handler returns the main handler. func (c *Context) Handler() HandlerFunc { return c.handlers.Last() diff --git a/context_test.go b/context_test.go index ea936b8..34cc71a 100644 --- a/context_test.go +++ b/context_test.go @@ -340,10 +340,26 @@ func TestContextHandlerName(t *testing.T) { assert.Regexp(t, "^(.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest$", c.HandlerName()) } +func TestContextHandlerNames(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest, func(c *Context) {}, handlerNameTest2} + + names := c.HandlerNames() + + assert.True(t, len(names) == 4) + for _, name := range names { + assert.Regexp(t, `^(.*/vendor/)?(github\.com/gin-gonic/gin\.){1}(TestContextHandlerNames\.func.*){0,1}(handlerNameTest.*){0,1}`, name) + } +} + func handlerNameTest(c *Context) { } +func handlerNameTest2(c *Context) { + +} + var handlerTest HandlerFunc = func(c *Context) { } From e207a3ce65323c3dda7cd8133fe14f4f1efd2500 Mon Sep 17 00:00:00 2001 From: Raphael Gavache Date: Tue, 26 Feb 2019 08:10:16 +0100 Subject: [PATCH 311/912] Fix context.Copy() race condition (#1020) * Fix context.Copy race condition * Update githubapi_test.go * fix code format --- context.go | 4 ++++ context_test.go | 2 ++ githubapi_test.go | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/context.go b/context.go index fa63ec8..7618cef 100644 --- a/context.go +++ b/context.go @@ -82,6 +82,10 @@ func (c *Context) Copy() *Context { cp.Writer = &cp.writermem cp.index = abortIndex cp.handlers = nil + cp.Keys = map[string]interface{}{} + for k, v := range c.Keys { + cp.Keys[k] = v + } return &cp } diff --git a/context_test.go b/context_test.go index 34cc71a..dc8ac30 100644 --- a/context_test.go +++ b/context_test.go @@ -331,6 +331,8 @@ func TestContextCopy(t *testing.T) { assert.Equal(t, cp.Keys, c.Keys) assert.Equal(t, cp.engine, c.engine) assert.Equal(t, cp.Params, c.Params) + cp.Set("foo", "notBar") + assert.False(t, cp.Keys["foo"] == c.Keys["foo"]) } func TestContextHandlerName(t *testing.T) { diff --git a/githubapi_test.go b/githubapi_test.go index 29aa158..fb74d65 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -346,6 +346,29 @@ func TestBindUriError(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w1.Code) } +func TestRaceContextCopy(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + router.GET("/test/copy/race", func(c *Context) { + c.Set("1", 0) + c.Set("2", 0) + + // Sending a copy of the Context to two separate routines + go readWriteKeys(c.Copy()) + go readWriteKeys(c.Copy()) + c.String(http.StatusOK, "run OK, no panics") + }) + w := performRequest(router, "GET", "/test/copy/race") + assert.Equal(t, "run OK, no panics", w.Body.String()) +} + +func readWriteKeys(c *Context) { + for { + c.Set("1", rand.Int()) + c.Set("2", c.Value("1")) + } +} + func githubConfigRouter(router *Engine) { for _, route := range githubAPI { router.Handle(route.method, route.path, func(c *Context) { From ccb105dbcb48cbcec988df8a46e90af3adf7c0ff Mon Sep 17 00:00:00 2001 From: Tudor Roman Date: Wed, 27 Feb 2019 13:56:29 +0200 Subject: [PATCH 312/912] add prefix from X-Forwarded-Prefix in redirectTrailingSlash (#1238) * add prefix from X-Forwarded-Prefix in redirectTrailingSlash * added test * fix path import --- gin.go | 14 +++++++++----- routes_test.go | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/gin.go b/gin.go index ad64c35..e28e957 100644 --- a/gin.go +++ b/gin.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "os" + "path" "sync" "github.com/gin-gonic/gin/render" @@ -438,17 +439,20 @@ func serveError(c *Context, code int, defaultMessage []byte) { func redirectTrailingSlash(c *Context) { req := c.Request - path := req.URL.Path + p := req.URL.Path + if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { + p = prefix + "/" + req.URL.Path + } code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { code = http.StatusTemporaryRedirect } - req.URL.Path = path + "/" - if length := len(path); length > 1 && path[length-1] == '/' { - req.URL.Path = path[:length-1] + req.URL.Path = p + "/" + if length := len(p); length > 1 && p[length-1] == '/' { + req.URL.Path = p[:length-1] } - debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) + debugPrint("redirecting request %d: %s --> %s", code, p, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) c.writermem.WriteHeaderNow() } diff --git a/routes_test.go b/routes_test.go index 8d50292..a842704 100644 --- a/routes_test.go +++ b/routes_test.go @@ -16,8 +16,16 @@ import ( "github.com/stretchr/testify/assert" ) -func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder { +type header struct { + Key string + Value string +} + +func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder { req, _ := http.NewRequest(method, path, nil) + for _, h := range headers { + req.Header.Add(h.Key, h.Value) + } w := httptest.NewRecorder() r.ServeHTTP(w, req) return w @@ -170,6 +178,13 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w = performRequest(router, "PUT", "/path4/") assert.Equal(t, http.StatusOK, w.Code) + w = performRequest(router, "GET", "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) + assert.Equal(t, "/api/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = performRequest(router, "GET", "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) + assert.Equal(t, 200, w.Code) + router.RedirectTrailingSlash = false w = performRequest(router, "GET", "/path/") From 7dfa6c936a3f3169979f1bbfa074c6915f055881 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Thu, 28 Feb 2019 17:43:27 +0300 Subject: [PATCH 313/912] fix #1784: correct error comparison on tests (#1785) --- context_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index dc8ac30..060a8e8 100644 --- a/context_test.go +++ b/context_test.go @@ -1242,22 +1242,24 @@ func TestContextError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) - c.Error(errors.New("first error")) // nolint: errcheck + firstErr := errors.New("first error") + c.Error(firstErr) // nolint: errcheck assert.Len(t, c.Errors, 1) assert.Equal(t, "Error #01: first error\n", c.Errors.String()) + secondErr := errors.New("second error") c.Error(&Error{ // nolint: errcheck - Err: errors.New("second error"), + Err: secondErr, Meta: "some data 2", Type: ErrorTypePublic, }) assert.Len(t, c.Errors, 2) - assert.Equal(t, errors.New("first error"), c.Errors[0].Err) + assert.Equal(t, firstErr, c.Errors[0].Err) assert.Nil(t, c.Errors[0].Meta) assert.Equal(t, ErrorTypePrivate, c.Errors[0].Type) - assert.Equal(t, errors.New("second error"), c.Errors[1].Err) + assert.Equal(t, secondErr, c.Errors[1].Err) assert.Equal(t, "some data 2", c.Errors[1].Meta) assert.Equal(t, ErrorTypePublic, c.Errors[1].Type) From 9bacadd3eab2e7271c456eedf2b02e4e09357d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 1 Mar 2019 07:11:02 +0800 Subject: [PATCH 314/912] remove docs dir (#1786) the post doc move https://gin-gonic.com/blog/ --- docs/how-to-build-an-effective-middleware.md | 137 ------------------- 1 file changed, 137 deletions(-) delete mode 100644 docs/how-to-build-an-effective-middleware.md diff --git a/docs/how-to-build-an-effective-middleware.md b/docs/how-to-build-an-effective-middleware.md deleted file mode 100644 index 568d572..0000000 --- a/docs/how-to-build-an-effective-middleware.md +++ /dev/null @@ -1,137 +0,0 @@ -# How to build one effective middleware? - -## Consitituent part - -The middleware has two parts: - - - part one is what is executed once, when you initialize your middleware. That's where you set up all the global objects, logicals etc. Everything that happens one per application lifetime. - - - part two is what executes on every request. For example, a database middleware you simply inject your "global" database object into the context. Once it's inside the context, you can retrieve it from within other middlewares and your handler function. - -```go -func funcName(params string) gin.HandlerFunc { - // <--- - // This is part one - // ---> - // The follow code is an example - if err := check(params); err != nil { - panic(err) - } - - return func(c *gin.Context) { - // <--- - // This is part two - // ---> - // The follow code is an example - c.Set("TestVar", params) - c.Next() - } -} -``` - -## Execution process - -Firstly, we have the follow example code: - -```go -func main() { - router := gin.Default() - - router.Use(globalMiddleware()) - - router.GET("/rest/n/api/*some", mid1(), mid2(), handler) - - router.Run() -} - -func globalMiddleware() gin.HandlerFunc { - fmt.Println("globalMiddleware...1") - - return func(c *gin.Context) { - fmt.Println("globalMiddleware...2") - c.Next() - fmt.Println("globalMiddleware...3") - } -} - -func handler(c *gin.Context) { - fmt.Println("exec handler.") -} - -func mid1() gin.HandlerFunc { - fmt.Println("mid1...1") - - return func(c *gin.Context) { - - fmt.Println("mid1...2") - c.Next() - fmt.Println("mid1...3") - } -} - -func mid2() gin.HandlerFunc { - fmt.Println("mid2...1") - - return func(c *gin.Context) { - fmt.Println("mid2...2") - c.Next() - fmt.Println("mid2...3") - } -} -``` - -According to [Consitituent part](#consitituent-part) said, when we run the gin process, **part one** will execute firstly and will print the follow information: - -```go -globalMiddleware...1 -mid1...1 -mid2...1 -``` - -And init order are: - -```go -globalMiddleware...1 - | - v -mid1...1 - | - v -mid2...1 -``` - -When we curl one request `curl -v localhost:8080/rest/n/api/some`, **part two** will execute their middleware and output the following information: - -```go -globalMiddleware...2 -mid1...2 -mid2...2 -exec handler. -mid2...3 -mid1...3 -globalMiddleware...3 -``` - -In other words, run order are: - -```go -globalMiddleware...2 - | - v -mid1...2 - | - v -mid2...2 - | - v -exec handler. - | - v -mid2...3 - | - v -mid1...3 - | - v -globalMiddleware...3 -``` From 2dd31930060c978a84507571f7a3106afbf9673b Mon Sep 17 00:00:00 2001 From: Equim Date: Fri, 1 Mar 2019 10:03:14 +0800 Subject: [PATCH 315/912] Support negotiation wildcards, fix #391 (#1112) * support negotiation wildcards, fix #391 * fix typo --- context.go | 13 ++++++++++++- context_test.go | 28 ++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 7618cef..08c420b 100644 --- a/context.go +++ b/context.go @@ -952,7 +952,18 @@ func (c *Context) NegotiateFormat(offered ...string) string { } for _, accepted := range c.Accepted { for _, offert := range offered { - if accepted == offert { + // According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers, + // therefore we can just iterate over the string without casting it into []rune + i := 0 + for ; i < len(accepted); i++ { + if accepted[i] == '*' || offert[i] == '*' { + return offert + } + if accepted[i] != offert[i] { + break + } + } + if i == len(accepted) { return offert } } diff --git a/context_test.go b/context_test.go index 060a8e8..483e168 100644 --- a/context_test.go +++ b/context_test.go @@ -1158,17 +1158,41 @@ func TestContextNegotiationFormat(t *testing.T) { func TestContextNegotiationFormatWithAccept(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML)) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML)) assert.Empty(t, c.NegotiateFormat(MIMEJSON)) } +func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("Accept", "*/*") + + assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") + assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") + assert.Equal(t, c.NegotiateFormat("application/*"), "application/*") + assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON) + assert.Equal(t, c.NegotiateFormat(MIMEXML), MIMEXML) + assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) + + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("Accept", "text/*") + + assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") + assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") + assert.Equal(t, c.NegotiateFormat("application/*"), "") + assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") + assert.Equal(t, c.NegotiateFormat(MIMEXML), "") + assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) +} + func TestContextNegotiationFormatCustom(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") c.Accepted = nil c.SetAccepted(MIMEJSON, MIMEXML) From ccb9e902956d15e354867918b105e1595a2f370a Mon Sep 17 00:00:00 2001 From: Emmanuel Goh Date: Fri, 1 Mar 2019 10:17:47 +0800 Subject: [PATCH 316/912] Extend context.File to allow for the content-dispositon attachments via a new method context.Attachment (#1260) * Add FileAttachment method to context to allow instant downloads with filenames * Add relevant tests for FileAttachment method --- context.go | 8 ++++++++ context_test.go | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/context.go b/context.go index 08c420b..e9735d2 100644 --- a/context.go +++ b/context.go @@ -6,6 +6,7 @@ package gin import ( "errors" + "fmt" "io" "io/ioutil" "math" @@ -880,6 +881,13 @@ func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) } +// FileAttachment writes the specified file into the body stream in an efficient way +// On the client side, the file will typically be downloaded with the given filename +func (c *Context) FileAttachment(filepath, filename string) { + c.Writer.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + http.ServeFile(c.Writer, c.Request, filepath) +} + // SSEvent writes a Server-Sent Event into the body stream. func (c *Context) SSEvent(name string, message interface{}) { c.Render(-1, sse.Event{ diff --git a/context_test.go b/context_test.go index 483e168..0da5fbe 100644 --- a/context_test.go +++ b/context_test.go @@ -979,6 +979,19 @@ func TestContextRenderFile(t *testing.T) { assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } +func TestContextRenderAttachment(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + newFilename := "new_filename.go" + + c.Request, _ = http.NewRequest("GET", "/", nil) + c.FileAttachment("./gin.go", newFilename) + + assert.Equal(t, 200, w.Code) + assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.HeaderMap.Get("Content-Disposition")) +} + // TestContextRenderYAML tests that the response is serialized as YAML // and Content-Type is set to application/x-yaml func TestContextRenderYAML(t *testing.T) { From 0feaf8cbd80da13be634b13fd28bfb2d6e357839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 1 Mar 2019 23:42:41 +0800 Subject: [PATCH 317/912] Split examples to alone repo (#1776) * split examples to alone repo * vendor * fix package error * add examples/README.md --- .travis.yml | 1 - Makefile | 9 +- README.md | 6 - examples/README.md | 3 + examples/app-engine/README.md | 8 - examples/app-engine/app.yaml | 8 - examples/app-engine/hello.go | 24 -- examples/assets-in-binary/README.md | 33 --- examples/assets-in-binary/assets.go | 34 --- examples/assets-in-binary/html/bar.tmpl | 4 - examples/assets-in-binary/html/index.tmpl | 4 - examples/assets-in-binary/main.go | 48 ---- examples/auto-tls/example1/main.go | 19 -- examples/auto-tls/example2/main.go | 26 --- examples/basic/main.go | 65 ------ examples/basic/main_test.go | 20 -- examples/custom-validation/server.go | 50 ----- examples/favicon/favicon.ico | Bin 1150 -> 0 bytes examples/favicon/main.go | 17 -- examples/graceful-shutdown/close/server.go | 45 ---- .../graceful-shutdown/server.go | 57 ----- examples/grpc/README.md | 19 -- examples/grpc/gin/main.go | 46 ---- examples/grpc/grpc/server.go | 34 --- examples/grpc/pb/helloworld.pb.go | 151 ------------- examples/grpc/pb/helloworld.proto | 37 ---- examples/http-pusher/assets/app.js | 1 - examples/http-pusher/main.go | 41 ---- examples/http-pusher/testdata/ca.pem | 15 -- examples/http-pusher/testdata/server.key | 16 -- examples/http-pusher/testdata/server.pem | 16 -- examples/http2/README.md | 18 -- examples/http2/main.go | 38 ---- examples/http2/testdata/ca.pem | 15 -- examples/http2/testdata/server.key | 16 -- examples/http2/testdata/server.pem | 16 -- examples/multiple-service/main.go | 74 ------- examples/new_relic/README.md | 30 --- examples/new_relic/main.go | 42 ---- examples/realtime-advanced/Makefile | 10 - examples/realtime-advanced/main.go | 42 ---- .../resources/room_login.templ.html | 208 ------------------ .../resources/static/epoch.min.css | 1 - .../resources/static/epoch.min.js | 114 ---------- .../resources/static/prismjs.min.css | 137 ------------ .../resources/static/prismjs.min.js | 5 - .../resources/static/realtime.js | 144 ------------ examples/realtime-advanced/rooms.go | 25 --- examples/realtime-advanced/routes.go | 96 -------- examples/realtime-advanced/stats.go | 59 ----- examples/realtime-chat/Makefile | 9 - examples/realtime-chat/main.go | 59 ----- examples/realtime-chat/rooms.go | 33 --- examples/realtime-chat/template.go | 44 ---- examples/struct-lvl-validations/README.md | 50 ----- examples/struct-lvl-validations/server.go | 64 ------ examples/template/main.go | 32 --- examples/upload-file/multiple/main.go | 39 ---- .../upload-file/multiple/public/index.html | 17 -- examples/upload-file/single/main.go | 36 --- examples/upload-file/single/public/index.html | 16 -- go.mod | 24 +- go.sum | 42 +--- vendor/vendor.json | 6 + 64 files changed, 25 insertions(+), 2393 deletions(-) create mode 100644 examples/README.md delete mode 100644 examples/app-engine/README.md delete mode 100644 examples/app-engine/app.yaml delete mode 100644 examples/app-engine/hello.go delete mode 100644 examples/assets-in-binary/README.md delete mode 100644 examples/assets-in-binary/assets.go delete mode 100644 examples/assets-in-binary/html/bar.tmpl delete mode 100644 examples/assets-in-binary/html/index.tmpl delete mode 100644 examples/assets-in-binary/main.go delete mode 100644 examples/auto-tls/example1/main.go delete mode 100644 examples/auto-tls/example2/main.go delete mode 100644 examples/basic/main.go delete mode 100644 examples/basic/main_test.go delete mode 100644 examples/custom-validation/server.go delete mode 100644 examples/favicon/favicon.ico delete mode 100644 examples/favicon/main.go delete mode 100644 examples/graceful-shutdown/close/server.go delete mode 100644 examples/graceful-shutdown/graceful-shutdown/server.go delete mode 100644 examples/grpc/README.md delete mode 100644 examples/grpc/gin/main.go delete mode 100644 examples/grpc/grpc/server.go delete mode 100644 examples/grpc/pb/helloworld.pb.go delete mode 100644 examples/grpc/pb/helloworld.proto delete mode 100644 examples/http-pusher/assets/app.js delete mode 100644 examples/http-pusher/main.go delete mode 100644 examples/http-pusher/testdata/ca.pem delete mode 100644 examples/http-pusher/testdata/server.key delete mode 100644 examples/http-pusher/testdata/server.pem delete mode 100644 examples/http2/README.md delete mode 100644 examples/http2/main.go delete mode 100644 examples/http2/testdata/ca.pem delete mode 100644 examples/http2/testdata/server.key delete mode 100644 examples/http2/testdata/server.pem delete mode 100644 examples/multiple-service/main.go delete mode 100644 examples/new_relic/README.md delete mode 100644 examples/new_relic/main.go delete mode 100644 examples/realtime-advanced/Makefile delete mode 100644 examples/realtime-advanced/main.go delete mode 100644 examples/realtime-advanced/resources/room_login.templ.html delete mode 100644 examples/realtime-advanced/resources/static/epoch.min.css delete mode 100644 examples/realtime-advanced/resources/static/epoch.min.js delete mode 100644 examples/realtime-advanced/resources/static/prismjs.min.css delete mode 100644 examples/realtime-advanced/resources/static/prismjs.min.js delete mode 100644 examples/realtime-advanced/resources/static/realtime.js delete mode 100644 examples/realtime-advanced/rooms.go delete mode 100644 examples/realtime-advanced/routes.go delete mode 100644 examples/realtime-advanced/stats.go delete mode 100644 examples/realtime-chat/Makefile delete mode 100644 examples/realtime-chat/main.go delete mode 100644 examples/realtime-chat/rooms.go delete mode 100644 examples/realtime-chat/template.go delete mode 100644 examples/struct-lvl-validations/README.md delete mode 100644 examples/struct-lvl-validations/server.go delete mode 100644 examples/template/main.go delete mode 100644 examples/upload-file/multiple/main.go delete mode 100644 examples/upload-file/multiple/public/index.html delete mode 100644 examples/upload-file/single/main.go delete mode 100644 examples/upload-file/single/public/index.html diff --git a/.travis.yml b/.travis.yml index be196fe..0039375 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,6 @@ go_import_path: github.com/gin-gonic/gin script: - make vet - make fmt-check - - make embedmd - make misspell-check - make test diff --git a/Makefile b/Makefile index 7ab57e2..51a6b91 100644 --- a/Makefile +++ b/Makefile @@ -52,12 +52,6 @@ deps: @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u github.com/kardianos/govendor; \ fi - @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/campoy/embedmd; \ - fi - -embedmd: - embedmd -d *.md .PHONY: lint lint: @@ -83,5 +77,4 @@ misspell: .PHONY: tools tools: go install golang.org/x/lint/golint; \ - go install github.com/client9/misspell/cmd/misspell; \ - go install github.com/campoy/embedmd; + go install github.com/client9/misspell/cmd/misspell; diff --git a/README.md b/README.md index 058eee2..a4ced64 100644 --- a/README.md +++ b/README.md @@ -728,7 +728,6 @@ When running the above example using the above the `curl` command, it returns er 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 @@ -1501,7 +1500,6 @@ func main() { example for 1-line LetsEncrypt HTTPS servers. -[embedmd]:# (examples/auto-tls/example1/main.go go) ```go package main @@ -1526,7 +1524,6 @@ func main() { example for custom autocert manager. -[embedmd]:# (examples/auto-tls/example2/main.go go) ```go package main @@ -1560,7 +1557,6 @@ func main() { 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 @@ -1660,7 +1656,6 @@ An alternative to endless: If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](./examples/graceful-shutdown) example with gin. -[embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) ```go // +build go1.8 @@ -1919,7 +1914,6 @@ performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. -[embedmd]:# (examples/http-pusher/main.go go) ```go package main diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..4b3b718 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,3 @@ +# Gin Examples + +## TODO diff --git a/examples/app-engine/README.md b/examples/app-engine/README.md deleted file mode 100644 index b3dd7c7..0000000 --- a/examples/app-engine/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Guide to run Gin under App Engine LOCAL Development Server - -1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.) -2. Download SDK for your platform from [here](https://cloud.google.com/appengine/docs/standard/go/download): `https://cloud.google.com/appengine/docs/standard/go/download` -3. Download Gin source code using: `$ go get github.com/gin-gonic/gin` -4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/app-engine/` -5. Run it: `$ dev_appserver.py .` (notice that you have to run this script by Python2) - diff --git a/examples/app-engine/app.yaml b/examples/app-engine/app.yaml deleted file mode 100644 index 5f20cf3..0000000 --- a/examples/app-engine/app.yaml +++ /dev/null @@ -1,8 +0,0 @@ -application: hello -version: 1 -runtime: go -api_version: go1 - -handlers: -- url: /.* - script: _go_app \ No newline at end of file diff --git a/examples/app-engine/hello.go b/examples/app-engine/hello.go deleted file mode 100644 index f569dad..0000000 --- a/examples/app-engine/hello.go +++ /dev/null @@ -1,24 +0,0 @@ -package hello - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -// This function's name is a must. App Engine uses it to drive the requests properly. -func init() { - // Starts a new Gin instance with no middle-ware - r := gin.New() - - // Define your handlers - r.GET("/", func(c *gin.Context) { - c.String(http.StatusOK, "Hello World!") - }) - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - // Handle all requests using net/http - http.Handle("/", r) -} diff --git a/examples/assets-in-binary/README.md b/examples/assets-in-binary/README.md deleted file mode 100644 index 0c23bb0..0000000 --- a/examples/assets-in-binary/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Building a single binary containing templates - -This is a complete example to create a single binary with the -[gin-gonic/gin][gin] Web Server with HTML templates. - -[gin]: https://github.com/gin-gonic/gin - -## How to use - -### Prepare Packages - -``` -go get github.com/gin-gonic/gin -go get github.com/jessevdk/go-assets-builder -``` - -### Generate assets.go - -``` -go-assets-builder html -o assets.go -``` - -### Build the server - -``` -go build -o assets-in-binary -``` - -### Run - -``` -./assets-in-binary -``` diff --git a/examples/assets-in-binary/assets.go b/examples/assets-in-binary/assets.go deleted file mode 100644 index dcc5c46..0000000 --- a/examples/assets-in-binary/assets.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "time" - - "github.com/jessevdk/go-assets" -) - -var _Assetsbfa8d115ce0617d89507412d5393a462f8e9b003 = "\n\n

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

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

Hello, {{.Foo}}

\n\n" - -// Assets returns go-assets FileSystem -var Assets = assets.NewFileSystem(map[string][]string{"/": {"html"}, "/html": {"bar.tmpl", "index.tmpl"}}, map[string]*assets.File{ - "/": { - Path: "/", - FileMode: 0x800001ed, - Mtime: time.Unix(1524365738, 1524365738517125470), - Data: nil, - }, "/html": { - Path: "/html", - FileMode: 0x800001ed, - Mtime: time.Unix(1524365491, 1524365491289799093), - Data: nil, - }, "/html/bar.tmpl": { - Path: "/html/bar.tmpl", - FileMode: 0x1a4, - Mtime: time.Unix(1524365491, 1524365491289611557), - Data: []byte(_Assetsbfa8d115ce0617d89507412d5393a462f8e9b003), - }, "/html/index.tmpl": { - Path: "/html/index.tmpl", - FileMode: 0x1a4, - Mtime: time.Unix(1524365491, 1524365491289995821), - Data: []byte(_Assets3737a75b5254ed1f6d588b40a3449721f9ea86c2), - }}, "") diff --git a/examples/assets-in-binary/html/bar.tmpl b/examples/assets-in-binary/html/bar.tmpl deleted file mode 100644 index c8e1c0f..0000000 --- a/examples/assets-in-binary/html/bar.tmpl +++ /dev/null @@ -1,4 +0,0 @@ - - -

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

- diff --git a/examples/assets-in-binary/html/index.tmpl b/examples/assets-in-binary/html/index.tmpl deleted file mode 100644 index 6904fd5..0000000 --- a/examples/assets-in-binary/html/index.tmpl +++ /dev/null @@ -1,4 +0,0 @@ - - -

Hello, {{.Foo}}

- diff --git a/examples/assets-in-binary/main.go b/examples/assets-in-binary/main.go deleted file mode 100644 index 27bc3b1..0000000 --- a/examples/assets-in-binary/main.go +++ /dev/null @@ -1,48 +0,0 @@ -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/main.go b/examples/auto-tls/example1/main.go deleted file mode 100644 index fa9f400..0000000 --- a/examples/auto-tls/example1/main.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - log.Fatal(autotls.Run(r, "example1.com", "example2.com")) -} diff --git a/examples/auto-tls/example2/main.go b/examples/auto-tls/example2/main.go deleted file mode 100644 index 0171868..0000000 --- a/examples/auto-tls/example2/main.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" - "golang.org/x/crypto/acme/autocert" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), - Cache: autocert.DirCache("/var/www/.cache"), - } - - log.Fatal(autotls.RunWithManager(r, &m)) -} diff --git a/examples/basic/main.go b/examples/basic/main.go deleted file mode 100644 index 1c9e0ac..0000000 --- a/examples/basic/main.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -var db = make(map[string]string) - -func setupRouter() *gin.Engine { - // Disable Console Color - // gin.DisableConsoleColor() - r := gin.Default() - - // Ping test - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - // Get user value - r.GET("/user/:name", func(c *gin.Context) { - user := c.Params.ByName("name") - value, ok := db[user] - if ok { - c.JSON(http.StatusOK, gin.H{"user": user, "value": value}) - } else { - c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"}) - } - }) - - // Authorized group (uses gin.BasicAuth() middleware) - // Same than: - // authorized := r.Group("/") - // authorized.Use(gin.BasicAuth(gin.Credentials{ - // "foo": "bar", - // "manu": "123", - //})) - authorized := r.Group("/", gin.BasicAuth(gin.Accounts{ - "foo": "bar", // user:foo password:bar - "manu": "123", // user:manu password:123 - })) - - authorized.POST("admin", func(c *gin.Context) { - user := c.MustGet(gin.AuthUserKey).(string) - - // Parse JSON - var json struct { - Value string `json:"value" binding:"required"` - } - - if c.Bind(&json) == nil { - db[user] = json.Value - c.JSON(http.StatusOK, gin.H{"status": "ok"}) - } - }) - - return r -} - -func main() { - r := setupRouter() - // Listen and Server in 0.0.0.0:8080 - r.Run(":8080") -} diff --git a/examples/basic/main_test.go b/examples/basic/main_test.go deleted file mode 100644 index 5eb8524..0000000 --- a/examples/basic/main_test.go +++ /dev/null @@ -1,20 +0,0 @@ -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, http.StatusOK, w.Code) - assert.Equal(t, "pong", w.Body.String()) -} diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go deleted file mode 100644 index 9238200..0000000 --- a/examples/custom-validation/server.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "net/http" - "reflect" - "time" - - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - "gopkg.in/go-playground/validator.v8" -) - -// Booking contains binded and validated data. -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/favicon/favicon.ico b/examples/favicon/favicon.ico deleted file mode 100644 index 3959cd7f9b13333df2f132f736c5f1d562278895..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmZvaZAep57{~u-FK%{oZPV!zqumdgW0lj0f*e`-B@(PiL9v7&C`hOvv*<%AsnCmM z1{IQ-Wf{?n%U;YZA+mQGi`IuSL&;vX7hRjRdwR}RV$i+(&bjCOpZ|03bM6B$x{XHA zXu~=U5Dy@Qpe@>o?9&907Ar*vum3Z+TrJ4TYEn6!ZB~b)J=Nj3J|jKdZO+d>z?Lj& z_>Uv9Ww+Nrr5c6J){<rb+kFDFx&2;ZqPs+vPbDNJxiNe8wtw=N&0AqM zcOowC@WP1`Pd?LV&T_`u9s(V?#9GE$d$rm#++a9y!(ypRwpflKCMItzha??@B<-87 z(;f9PR?mT^uRv=S;09x7Del60;CM)-s^@zB;Z{kiHbqD84*MC~Los%yRyWo#)=b{h z#QD3xRReH|V%h}LfOCC7GksU{PmQKwjaDo2mXP-%)qNq6u}&Y*MP9+}6F@NoTK@R2 zP_C|fe|5$>T2-osD8^2c?j<~Pfu3)`8}jxNo_zpKI7zsP<59r#D-m79ynF#Xumbn# z{X@j3OvZlrfgou=h@NW3g#Qq6m8hUU|CWjodXQ=udBa%0jlhHneqtnDl9WM7;#`tK zJSwVhe?o%ri~4U8^*X)&Xh zx9Du{kiWw?dGcUb7csINYBHJn)mG~eoK8P-ayVQrWot$TR|xKKe11%47^~HG!({SM zZ$F)pmNxuOXf7B3o{^zsFId1abLIf$23D`;g80HheyyN@^XzQDUzU9OoZinxyEuqKAfmpL|X4q(TQVDg3yLC>a53eU?Md^Kmz%hLJtUsn|s zegCv^qr!`egXgQOL#DDaWy~9SFd{Xz2M&iX)o%ZKv$T|#)YK?vcJ2(^FE0&)6%-lPA8Q&?sB<0v3N1ZvSkD1^X3h2@%dik{d=zVczBH0%jvIh>Q6<# zXwkPLxw(DI3kp7ra|274KB09EcI_HTuB;3gtEvM3=#7mZP**pKy?X=J%F5yR+S;FE kdSEK>WfFCrj=Hk~C=--X$BaX)h1R8x#EIB`qMMHYHxij({r~^~ diff --git a/examples/favicon/main.go b/examples/favicon/main.go deleted file mode 100644 index d32ca09..0000000 --- a/examples/favicon/main.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/gin-gonic/gin" - "github.com/thinkerou/favicon" -) - -func main() { - app := gin.Default() - app.Use(favicon.New("./favicon.ico")) - app.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "Hello favicon.") - }) - app.Run(":8080") -} diff --git a/examples/graceful-shutdown/close/server.go b/examples/graceful-shutdown/close/server.go deleted file mode 100644 index 9c4e90f..0000000 --- a/examples/graceful-shutdown/close/server.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build go1.8 - -package main - -import ( - "log" - "net/http" - "os" - "os/signal" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - router.GET("/", func(c *gin.Context) { - c.String(http.StatusOK, "Welcome Gin Server") - }) - - server := &http.Server{ - Addr: ":8080", - Handler: router, - } - - quit := make(chan os.Signal) - signal.Notify(quit, os.Interrupt) - - go func() { - <-quit - log.Println("receive interrupt signal") - if err := server.Close(); err != nil { - log.Fatal("Server Close:", err) - } - }() - - if err := server.ListenAndServe(); err != nil { - if err == http.ErrServerClosed { - log.Println("Server closed under request") - } else { - log.Fatal("Server closed unexpect") - } - } - - log.Println("Server exiting") -} diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go deleted file mode 100644 index 999a209..0000000 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ /dev/null @@ -1,57 +0,0 @@ -// +build go1.8 - -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - router.GET("/", func(c *gin.Context) { - time.Sleep(5 * time.Second) - c.String(http.StatusOK, "Welcome Gin Server") - }) - - srv := &http.Server{ - Addr: ":8080", - Handler: router, - } - - go func() { - // service connections - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) - } - }() - - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 5 seconds. - quit := make(chan os.Signal) - // kill (no param) default send syscanll.SIGTERM - // kill -2 is syscall.SIGINT - // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Println("Shutdown Server ...") - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server Shutdown:", err) - } - // catching ctx.Done(). timeout of 5 seconds. - select { - case <-ctx.Done(): - log.Println("timeout of 5 seconds.") - } - log.Println("Server exiting") -} diff --git a/examples/grpc/README.md b/examples/grpc/README.md deleted file mode 100644 index a96d3c1..0000000 --- a/examples/grpc/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## How to run this example - -1. run grpc server - -```sh -$ go run grpc/server.go -``` - -2. run gin server - -```sh -$ go run gin/main.go -``` - -3. use curl command to test it - -```sh -$ curl -v 'http://localhost:8052/rest/n/thinkerou' -``` diff --git a/examples/grpc/gin/main.go b/examples/grpc/gin/main.go deleted file mode 100644 index 820e65a..0000000 --- a/examples/grpc/gin/main.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - - "github.com/gin-gonic/gin" - pb "github.com/gin-gonic/gin/examples/grpc/pb" - "google.golang.org/grpc" -) - -func main() { - // Set up a connection to the server. - conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) - if err != nil { - log.Fatalf("did not connect: %v", err) - } - defer conn.Close() - client := pb.NewGreeterClient(conn) - - // Set up a http server. - r := gin.Default() - r.GET("/rest/n/:name", func(c *gin.Context) { - name := c.Param("name") - - // Contact the server and print out its response. - req := &pb.HelloRequest{Name: name} - res, err := client.SayHello(c, req) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{ - "error": err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "result": fmt.Sprint(res.Message), - }) - }) - - // Run http server - if err := r.Run(":8052"); err != nil { - log.Fatalf("could not run server: %v", err) - } -} diff --git a/examples/grpc/grpc/server.go b/examples/grpc/grpc/server.go deleted file mode 100644 index d9bf9fc..0000000 --- a/examples/grpc/grpc/server.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "log" - "net" - - pb "github.com/gin-gonic/gin/examples/grpc/pb" - "golang.org/x/net/context" - "google.golang.org/grpc" - "google.golang.org/grpc/reflection" -) - -// server is used to implement helloworld.GreeterServer. -type server struct{} - -// SayHello implements helloworld.GreeterServer -func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { - return &pb.HelloReply{Message: "Hello " + in.Name}, nil -} - -func main() { - lis, err := net.Listen("tcp", ":50051") - if err != nil { - log.Fatalf("failed to listen: %v", err) - } - s := grpc.NewServer() - pb.RegisterGreeterServer(s, &server{}) - - // Register reflection service on gRPC server. - reflection.Register(s) - if err := s.Serve(lis); err != nil { - log.Fatalf("failed to serve: %v", err) - } -} diff --git a/examples/grpc/pb/helloworld.pb.go b/examples/grpc/pb/helloworld.pb.go deleted file mode 100644 index c8c8942..0000000 --- a/examples/grpc/pb/helloworld.pb.go +++ /dev/null @@ -1,151 +0,0 @@ -// Code generated by protoc-gen-go. -// source: helloworld.proto -// DO NOT EDIT! - -/* -Package helloworld is a generated protocol buffer package. - -It is generated from these files: - helloworld.proto - -It has these top-level messages: - HelloRequest - HelloReply -*/ -package helloworld - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package - -// The request message containing the user's name. -type HelloRequest struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` -} - -func (m *HelloRequest) Reset() { *m = HelloRequest{} } -func (m *HelloRequest) String() string { return proto.CompactTextString(m) } -func (*HelloRequest) ProtoMessage() {} -func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } - -// The response message containing the greetings -type HelloReply struct { - Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` -} - -func (m *HelloReply) Reset() { *m = HelloReply{} } -func (m *HelloReply) String() string { return proto.CompactTextString(m) } -func (*HelloReply) ProtoMessage() {} -func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } - -func init() { - proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest") - proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// Client API for Greeter service - -type GreeterClient interface { - // Sends a greeting - SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) -} - -type greeterClient struct { - cc *grpc.ClientConn -} - -func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { - return &greeterClient{cc} -} - -func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { - out := new(HelloReply) - err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for Greeter service - -type GreeterServer interface { - // Sends a greeting - SayHello(context.Context, *HelloRequest) (*HelloReply, error) -} - -func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { - s.RegisterService(&_Greeter_serviceDesc, srv) -} - -func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(HelloRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(GreeterServer).SayHello(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/helloworld.Greeter/SayHello", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) - } - return interceptor(ctx, in, info, handler) -} - -var _Greeter_serviceDesc = grpc.ServiceDesc{ - ServiceName: "helloworld.Greeter", - HandlerType: (*GreeterServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "SayHello", - Handler: _Greeter_SayHello_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "helloworld.proto", -} - -func init() { proto.RegisterFile("helloworld.proto", fileDescriptor0) } - -var fileDescriptor0 = []byte{ - // 174 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, - 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88, - 0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, - 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, - 0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, - 0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a, - 0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64, - 0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x00, 0xad, 0x50, 0x62, 0x70, 0x32, 0xe0, 0x92, 0xce, 0xcc, 0xd7, - 0x4b, 0x2f, 0x2a, 0x48, 0xd6, 0x4b, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x2d, 0x46, 0x52, 0xeb, - 0xc4, 0x0f, 0x56, 0x1c, 0x0e, 0x62, 0x07, 0x80, 0xbc, 0x14, 0xc0, 0x98, 0xc4, 0x06, 0xf6, 0x9b, - 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00, -} diff --git a/examples/grpc/pb/helloworld.proto b/examples/grpc/pb/helloworld.proto deleted file mode 100644 index d79a6a0..0000000 --- a/examples/grpc/pb/helloworld.proto +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2015 gRPC authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -option java_multiple_files = true; -option java_package = "io.grpc.examples.helloworld"; -option java_outer_classname = "HelloWorldProto"; - -package helloworld; - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) {} -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings -message HelloReply { - string message = 1; -} diff --git a/examples/http-pusher/assets/app.js b/examples/http-pusher/assets/app.js deleted file mode 100644 index 05271b6..0000000 --- a/examples/http-pusher/assets/app.js +++ /dev/null @@ -1 +0,0 @@ -console.log("http2 pusher"); diff --git a/examples/http-pusher/main.go b/examples/http-pusher/main.go deleted file mode 100644 index d4f33aa..0000000 --- a/examples/http-pusher/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "html/template" - "log" - - "github.com/gin-gonic/gin" -) - -var html = template.Must(template.New("https").Parse(` - - - Https Test - - - -

Welcome, Ginner!

- - -`)) - -func main() { - r := gin.Default() - r.Static("/assets", "./assets") - r.SetHTMLTemplate(html) - - r.GET("/", func(c *gin.Context) { - if pusher := c.Writer.Pusher(); pusher != nil { - // use pusher.Push() to do server push - if err := pusher.Push("/assets/app.js", nil); err != nil { - log.Printf("Failed to push: %v", err) - } - } - c.HTML(200, "https", gin.H{ - "status": "success", - }) - }) - - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") -} diff --git a/examples/http-pusher/testdata/ca.pem b/examples/http-pusher/testdata/ca.pem deleted file mode 100644 index 6c8511a..0000000 --- a/examples/http-pusher/testdata/ca.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla -Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 -YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT -BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 -+L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu -g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd -Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau -sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m -oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG -Dfcog5wrJytaQ6UA0wE= ------END CERTIFICATE----- diff --git a/examples/http-pusher/testdata/server.key b/examples/http-pusher/testdata/server.key deleted file mode 100644 index 143a5b8..0000000 --- a/examples/http-pusher/testdata/server.key +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD -M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf -3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY -AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm -V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY -tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p -dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q -K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR -81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff -DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd -aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 -ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 -XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe -F98XJ7tIFfJq ------END PRIVATE KEY----- diff --git a/examples/http-pusher/testdata/server.pem b/examples/http-pusher/testdata/server.pem deleted file mode 100644 index f3d43fc..0000000 --- a/examples/http-pusher/testdata/server.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET -MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ -dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx -MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV -BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 -ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco -LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg -zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd -9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw -CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy -em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G -CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 -hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh -y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 ------END CERTIFICATE----- diff --git a/examples/http2/README.md b/examples/http2/README.md deleted file mode 100644 index 42dd4b8..0000000 --- a/examples/http2/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## How to generate RSA private key and digital certificate - -1. Install Openssl - -Please visit https://github.com/openssl/openssl to get pkg and install. - -2. Generate RSA private key - -```sh -$ mkdir testdata -$ openssl genrsa -out ./testdata/server.key 2048 -``` - -3. Generate digital certificate - -```sh -$ openssl req -new -x509 -key ./testdata/server.key -out ./testdata/server.pem -days 365 -``` diff --git a/examples/http2/main.go b/examples/http2/main.go deleted file mode 100644 index 6598a4c..0000000 --- a/examples/http2/main.go +++ /dev/null @@ -1,38 +0,0 @@ -package main - -import ( - "html/template" - "log" - "net/http" - "os" - - "github.com/gin-gonic/gin" -) - -var html = template.Must(template.New("https").Parse(` - - - Https Test - - -

Welcome, Ginner!

- - -`)) - -func main() { - logger := log.New(os.Stderr, "", 0) - logger.Println("[WARNING] DON'T USE THE EMBED CERTS FROM THIS EXAMPLE IN PRODUCTION ENVIRONMENT, GENERATE YOUR OWN!") - - r := gin.Default() - r.SetHTMLTemplate(html) - - r.GET("/welcome", func(c *gin.Context) { - c.HTML(http.StatusOK, "https", gin.H{ - "status": "success", - }) - }) - - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") -} diff --git a/examples/http2/testdata/ca.pem b/examples/http2/testdata/ca.pem deleted file mode 100644 index 6c8511a..0000000 --- a/examples/http2/testdata/ca.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla -Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 -YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT -BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 -+L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu -g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd -Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau -sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m -oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG -Dfcog5wrJytaQ6UA0wE= ------END CERTIFICATE----- diff --git a/examples/http2/testdata/server.key b/examples/http2/testdata/server.key deleted file mode 100644 index 143a5b8..0000000 --- a/examples/http2/testdata/server.key +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD -M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf -3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY -AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm -V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY -tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p -dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q -K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR -81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff -DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd -aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 -ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 -XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe -F98XJ7tIFfJq ------END PRIVATE KEY----- diff --git a/examples/http2/testdata/server.pem b/examples/http2/testdata/server.pem deleted file mode 100644 index f3d43fc..0000000 --- a/examples/http2/testdata/server.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET -MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ -dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx -MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV -BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 -ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco -LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg -zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd -9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw -CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy -em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G -CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 -hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh -y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 ------END CERTIFICATE----- diff --git a/examples/multiple-service/main.go b/examples/multiple-service/main.go deleted file mode 100644 index ceddaa2..0000000 --- a/examples/multiple-service/main.go +++ /dev/null @@ -1,74 +0,0 @@ -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/new_relic/README.md b/examples/new_relic/README.md deleted file mode 100644 index 70f1494..0000000 --- a/examples/new_relic/README.md +++ /dev/null @@ -1,30 +0,0 @@ -The [New Relic Go Agent](https://github.com/newrelic/go-agent) provides a nice middleware for the stdlib handler signature. -The following is an adaptation of that middleware for Gin. - -```golang -const ( - // NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context - NewRelicTxnKey = "NewRelicTxnKey" -) - -// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler -func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc { - return func(ctx *gin.Context) { - txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request) - defer txn.End() - ctx.Set(NewRelicTxnKey, txn) - ctx.Next() - } -} -``` -and in `main.go` or equivalent... -```golang -router := gin.Default() -cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY")) -app, err := newrelic.NewApplication(cfg) -if err != nil { - log.Printf("failed to make new_relic app: %v", err) -} else { - router.Use(adapters.NewRelicMonitoring(app)) -} - ``` diff --git a/examples/new_relic/main.go b/examples/new_relic/main.go deleted file mode 100644 index f85f783..0000000 --- a/examples/new_relic/main.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "log" - "net/http" - "os" - - "github.com/gin-gonic/gin" - "github.com/newrelic/go-agent" -) - -const ( - // NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context - NewRelicTxnKey = "NewRelicTxnKey" -) - -// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler -func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc { - return func(ctx *gin.Context) { - txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request) - defer txn.End() - ctx.Set(NewRelicTxnKey, txn) - ctx.Next() - } -} - -func main() { - router := gin.Default() - - cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY")) - app, err := newrelic.NewApplication(cfg) - if err != nil { - log.Printf("failed to make new_relic app: %v", err) - } else { - router.Use(NewRelicMonitoring(app)) - } - - router.GET("/", func(c *gin.Context) { - c.String(http.StatusOK, "Hello World!\n") - }) - router.Run() -} diff --git a/examples/realtime-advanced/Makefile b/examples/realtime-advanced/Makefile deleted file mode 100644 index 104ce80..0000000 --- a/examples/realtime-advanced/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -all: deps build - -.PHONY: deps -deps: - go get -d -v github.com/dustin/go-broadcast/... - go get -d -v github.com/manucorporat/stats/... - -.PHONY: build -build: deps - go build -o realtime-advanced main.go rooms.go routes.go stats.go diff --git a/examples/realtime-advanced/main.go b/examples/realtime-advanced/main.go deleted file mode 100644 index f3ead47..0000000 --- a/examples/realtime-advanced/main.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "fmt" - "runtime" - - "github.com/gin-gonic/gin" -) - -func main() { - ConfigRuntime() - StartWorkers() - StartGin() -} - -// ConfigRuntime sets the number of operating system threads. -func ConfigRuntime() { - nuCPU := runtime.NumCPU() - runtime.GOMAXPROCS(nuCPU) - fmt.Printf("Running with %d CPUs\n", nuCPU) -} - -// StartWorkers start starsWorker by goroutine. -func StartWorkers() { - go statsWorker() -} - -// StartGin starts gin web server with setting router. -func StartGin() { - gin.SetMode(gin.ReleaseMode) - - router := gin.New() - router.Use(rateLimit, gin.Recovery()) - router.LoadHTMLGlob("resources/*.templ.html") - router.Static("/static", "resources/static") - router.GET("/", index) - router.GET("/room/:roomid", roomGET) - router.POST("/room-post/:roomid", roomPOST) - router.GET("/stream/:roomid", streamRoom) - - router.Run(":80") -} diff --git a/examples/realtime-advanced/resources/room_login.templ.html b/examples/realtime-advanced/resources/room_login.templ.html deleted file mode 100644 index 905c012..0000000 --- a/examples/realtime-advanced/resources/room_login.templ.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - Server-Sent Events. Room "{{.roomid}}" - - - - - - - - - - - - - - - - - - - - - - - -
-
-

Server-Sent Events in Go

-

Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection. It is not websockets. Learn more.

-

The chat and the charts data is provided in realtime using the SSE implementation of Gin Framework.

-
-
-
- - - - - - - - -
NickMessage
-
- {{if .nick}} -
-
- -
-
{{.nick}}
- -
-
- -
- {{else}} -
- Join the SSE real-time chat -
- -
-
- -
-
- {{end}} -
-
-
-

- ◼︎ Users
- ◼︎ Inbound messages / sec
- ◼︎ Outbound messages / sec
-

-
-
-
-
-
-
-

Realtime server Go stats

-
-

Memory usage

-

-

-

-

- ◼︎ Heap bytes
- ◼︎ Stack bytes
-

-
-
-

Allocations per second

-

-

-

-

- ◼︎ Mallocs / sec
- ◼︎ Frees / sec
-

-
-
-
-

MIT Open Sourced

- -
- -

Server-side (Go)

-
func streamRoom(c *gin.Context) {
-    roomid := c.ParamValue("roomid")
-    listener := openListener(roomid)
-    statsTicker := time.NewTicker(1 * time.Second)
-    defer closeListener(roomid, listener)
-    defer statsTicker.Stop()
-
-    c.Stream(func(w io.Writer) bool {
-        select {
-        case msg := <-listener:
-            c.SSEvent("message", msg)
-        case <-statsTicker.C:
-            c.SSEvent("stats", Stats())
-        }
-        return true
-    })
-}
-
-
-

Client-side (JS)

-
function StartSSE(roomid) {
-    var source = new EventSource('/stream/'+roomid);
-    source.addEventListener('message', newChatMessage, false);
-    source.addEventListener('stats', stats, false);
-}
-
-
-
-
-

SSE package

-
import "github.com/manucorporat/sse"
-
-func httpHandler(w http.ResponseWriter, req *http.Request) {
-    // data can be a primitive like a string, an integer or a float
-    sse.Encode(w, sse.Event{
-        Event: "message",
-        Data:  "some data\nmore data",
-    })
-
-    // also a complex type, like a map, a struct or a slice
-    sse.Encode(w, sse.Event{
-        Id:    "124",
-        Event: "message",
-        Data: map[string]interface{}{
-            "user":    "manu",
-            "date":    time.Now().Unix(),
-            "content": "hi!",
-        },
-    })
-}
-
event: message
-data: some data\\nmore data
-
-id: 124
-event: message
-data: {"content":"hi!","date":1431540810,"user":"manu"}
-
-
-
- -
- - diff --git a/examples/realtime-advanced/resources/static/epoch.min.css b/examples/realtime-advanced/resources/static/epoch.min.css deleted file mode 100644 index 47a80cd..0000000 --- a/examples/realtime-advanced/resources/static/epoch.min.css +++ /dev/null @@ -1 +0,0 @@ -.epoch .axis path,.epoch .axis line{shape-rendering:crispEdges;}.epoch .axis.canvas .tick line{shape-rendering:geometricPrecision;}div#_canvas_css_reference{width:0;height:0;position:absolute;top:-1000px;left:-1000px;}div#_canvas_css_reference svg{position:absolute;width:0;height:0;top:-1000px;left:-1000px;}.epoch{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12pt;}.epoch .axis path,.epoch .axis line{fill:none;stroke:#000;}.epoch .axis .tick text{font-size:9pt;}.epoch .line{fill:none;stroke-width:2px;}.epoch.sparklines .line{stroke-width:1px;}.epoch .area{stroke:none;}.epoch .arc.pie{stroke:#fff;stroke-width:1.5px;}.epoch .arc.pie text{stroke:none;fill:white;font-size:9pt;}.epoch .gauge-labels .value{text-anchor:middle;font-size:140%;fill:#666;}.epoch.gauge-tiny{width:120px;height:90px;}.epoch.gauge-tiny .gauge-labels .value{font-size:80%;}.epoch.gauge-tiny .gauge .arc.outer{stroke-width:2px;}.epoch.gauge-small{width:180px;height:135px;}.epoch.gauge-small .gauge-labels .value{font-size:120%;}.epoch.gauge-small .gauge .arc.outer{stroke-width:3px;}.epoch.gauge-medium{width:240px;height:180px;}.epoch.gauge-medium .gauge .arc.outer{stroke-width:3px;}.epoch.gauge-large{width:320px;height:240px;}.epoch.gauge-large .gauge-labels .value{font-size:180%;}.epoch .gauge .arc.outer{stroke-width:4px;stroke:#666;}.epoch .gauge .arc.inner{stroke-width:1px;stroke:#555;}.epoch .gauge .tick{stroke-width:1px;stroke:#555;}.epoch .gauge .needle{fill:orange;}.epoch .gauge .needle-base{fill:#666;}.epoch div.ref.category1,.epoch.category10 div.ref.category1{background-color:#1f77b4;}.epoch .category1 .line,.epoch.category10 .category1 .line{stroke:#1f77b4;}.epoch .category1 .area,.epoch .category1 .dot,.epoch.category10 .category1 .area,.epoch.category10 .category1 .dot{fill:#1f77b4;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category1 path,.epoch.category10 .arc.category1 path{fill:#1f77b4;}.epoch .bar.category1,.epoch.category10 .bar.category1{fill:#1f77b4;}.epoch div.ref.category2,.epoch.category10 div.ref.category2{background-color:#ff7f0e;}.epoch .category2 .line,.epoch.category10 .category2 .line{stroke:#ff7f0e;}.epoch .category2 .area,.epoch .category2 .dot,.epoch.category10 .category2 .area,.epoch.category10 .category2 .dot{fill:#ff7f0e;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category2 path,.epoch.category10 .arc.category2 path{fill:#ff7f0e;}.epoch .bar.category2,.epoch.category10 .bar.category2{fill:#ff7f0e;}.epoch div.ref.category3,.epoch.category10 div.ref.category3{background-color:#2ca02c;}.epoch .category3 .line,.epoch.category10 .category3 .line{stroke:#2ca02c;}.epoch .category3 .area,.epoch .category3 .dot,.epoch.category10 .category3 .area,.epoch.category10 .category3 .dot{fill:#2ca02c;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category3 path,.epoch.category10 .arc.category3 path{fill:#2ca02c;}.epoch .bar.category3,.epoch.category10 .bar.category3{fill:#2ca02c;}.epoch div.ref.category4,.epoch.category10 div.ref.category4{background-color:#d62728;}.epoch .category4 .line,.epoch.category10 .category4 .line{stroke:#d62728;}.epoch .category4 .area,.epoch .category4 .dot,.epoch.category10 .category4 .area,.epoch.category10 .category4 .dot{fill:#d62728;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category4 path,.epoch.category10 .arc.category4 path{fill:#d62728;}.epoch .bar.category4,.epoch.category10 .bar.category4{fill:#d62728;}.epoch div.ref.category5,.epoch.category10 div.ref.category5{background-color:#9467bd;}.epoch .category5 .line,.epoch.category10 .category5 .line{stroke:#9467bd;}.epoch .category5 .area,.epoch .category5 .dot,.epoch.category10 .category5 .area,.epoch.category10 .category5 .dot{fill:#9467bd;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category5 path,.epoch.category10 .arc.category5 path{fill:#9467bd;}.epoch .bar.category5,.epoch.category10 .bar.category5{fill:#9467bd;}.epoch div.ref.category6,.epoch.category10 div.ref.category6{background-color:#8c564b;}.epoch .category6 .line,.epoch.category10 .category6 .line{stroke:#8c564b;}.epoch .category6 .area,.epoch .category6 .dot,.epoch.category10 .category6 .area,.epoch.category10 .category6 .dot{fill:#8c564b;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category6 path,.epoch.category10 .arc.category6 path{fill:#8c564b;}.epoch .bar.category6,.epoch.category10 .bar.category6{fill:#8c564b;}.epoch div.ref.category7,.epoch.category10 div.ref.category7{background-color:#e377c2;}.epoch .category7 .line,.epoch.category10 .category7 .line{stroke:#e377c2;}.epoch .category7 .area,.epoch .category7 .dot,.epoch.category10 .category7 .area,.epoch.category10 .category7 .dot{fill:#e377c2;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category7 path,.epoch.category10 .arc.category7 path{fill:#e377c2;}.epoch .bar.category7,.epoch.category10 .bar.category7{fill:#e377c2;}.epoch div.ref.category8,.epoch.category10 div.ref.category8{background-color:#7f7f7f;}.epoch .category8 .line,.epoch.category10 .category8 .line{stroke:#7f7f7f;}.epoch .category8 .area,.epoch .category8 .dot,.epoch.category10 .category8 .area,.epoch.category10 .category8 .dot{fill:#7f7f7f;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category8 path,.epoch.category10 .arc.category8 path{fill:#7f7f7f;}.epoch .bar.category8,.epoch.category10 .bar.category8{fill:#7f7f7f;}.epoch div.ref.category9,.epoch.category10 div.ref.category9{background-color:#bcbd22;}.epoch .category9 .line,.epoch.category10 .category9 .line{stroke:#bcbd22;}.epoch .category9 .area,.epoch .category9 .dot,.epoch.category10 .category9 .area,.epoch.category10 .category9 .dot{fill:#bcbd22;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category9 path,.epoch.category10 .arc.category9 path{fill:#bcbd22;}.epoch .bar.category9,.epoch.category10 .bar.category9{fill:#bcbd22;}.epoch div.ref.category10,.epoch.category10 div.ref.category10{background-color:#17becf;}.epoch .category10 .line,.epoch.category10 .category10 .line{stroke:#17becf;}.epoch .category10 .area,.epoch .category10 .dot,.epoch.category10 .category10 .area,.epoch.category10 .category10 .dot{fill:#17becf;stroke:rgba(0, 0, 0, 0);}.epoch .arc.category10 path,.epoch.category10 .arc.category10 path{fill:#17becf;}.epoch .bar.category10,.epoch.category10 .bar.category10{fill:#17becf;}.epoch.category20 div.ref.category1{background-color:#1f77b4;}.epoch.category20 .category1 .line{stroke:#1f77b4;}.epoch.category20 .category1 .area,.epoch.category20 .category1 .dot{fill:#1f77b4;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category1 path{fill:#1f77b4;}.epoch.category20 .bar.category1{fill:#1f77b4;}.epoch.category20 div.ref.category2{background-color:#aec7e8;}.epoch.category20 .category2 .line{stroke:#aec7e8;}.epoch.category20 .category2 .area,.epoch.category20 .category2 .dot{fill:#aec7e8;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category2 path{fill:#aec7e8;}.epoch.category20 .bar.category2{fill:#aec7e8;}.epoch.category20 div.ref.category3{background-color:#ff7f0e;}.epoch.category20 .category3 .line{stroke:#ff7f0e;}.epoch.category20 .category3 .area,.epoch.category20 .category3 .dot{fill:#ff7f0e;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category3 path{fill:#ff7f0e;}.epoch.category20 .bar.category3{fill:#ff7f0e;}.epoch.category20 div.ref.category4{background-color:#ffbb78;}.epoch.category20 .category4 .line{stroke:#ffbb78;}.epoch.category20 .category4 .area,.epoch.category20 .category4 .dot{fill:#ffbb78;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category4 path{fill:#ffbb78;}.epoch.category20 .bar.category4{fill:#ffbb78;}.epoch.category20 div.ref.category5{background-color:#2ca02c;}.epoch.category20 .category5 .line{stroke:#2ca02c;}.epoch.category20 .category5 .area,.epoch.category20 .category5 .dot{fill:#2ca02c;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category5 path{fill:#2ca02c;}.epoch.category20 .bar.category5{fill:#2ca02c;}.epoch.category20 div.ref.category6{background-color:#98df8a;}.epoch.category20 .category6 .line{stroke:#98df8a;}.epoch.category20 .category6 .area,.epoch.category20 .category6 .dot{fill:#98df8a;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category6 path{fill:#98df8a;}.epoch.category20 .bar.category6{fill:#98df8a;}.epoch.category20 div.ref.category7{background-color:#d62728;}.epoch.category20 .category7 .line{stroke:#d62728;}.epoch.category20 .category7 .area,.epoch.category20 .category7 .dot{fill:#d62728;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category7 path{fill:#d62728;}.epoch.category20 .bar.category7{fill:#d62728;}.epoch.category20 div.ref.category8{background-color:#ff9896;}.epoch.category20 .category8 .line{stroke:#ff9896;}.epoch.category20 .category8 .area,.epoch.category20 .category8 .dot{fill:#ff9896;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category8 path{fill:#ff9896;}.epoch.category20 .bar.category8{fill:#ff9896;}.epoch.category20 div.ref.category9{background-color:#9467bd;}.epoch.category20 .category9 .line{stroke:#9467bd;}.epoch.category20 .category9 .area,.epoch.category20 .category9 .dot{fill:#9467bd;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category9 path{fill:#9467bd;}.epoch.category20 .bar.category9{fill:#9467bd;}.epoch.category20 div.ref.category10{background-color:#c5b0d5;}.epoch.category20 .category10 .line{stroke:#c5b0d5;}.epoch.category20 .category10 .area,.epoch.category20 .category10 .dot{fill:#c5b0d5;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category10 path{fill:#c5b0d5;}.epoch.category20 .bar.category10{fill:#c5b0d5;}.epoch.category20 div.ref.category11{background-color:#8c564b;}.epoch.category20 .category11 .line{stroke:#8c564b;}.epoch.category20 .category11 .area,.epoch.category20 .category11 .dot{fill:#8c564b;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category11 path{fill:#8c564b;}.epoch.category20 .bar.category11{fill:#8c564b;}.epoch.category20 div.ref.category12{background-color:#c49c94;}.epoch.category20 .category12 .line{stroke:#c49c94;}.epoch.category20 .category12 .area,.epoch.category20 .category12 .dot{fill:#c49c94;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category12 path{fill:#c49c94;}.epoch.category20 .bar.category12{fill:#c49c94;}.epoch.category20 div.ref.category13{background-color:#e377c2;}.epoch.category20 .category13 .line{stroke:#e377c2;}.epoch.category20 .category13 .area,.epoch.category20 .category13 .dot{fill:#e377c2;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category13 path{fill:#e377c2;}.epoch.category20 .bar.category13{fill:#e377c2;}.epoch.category20 div.ref.category14{background-color:#f7b6d2;}.epoch.category20 .category14 .line{stroke:#f7b6d2;}.epoch.category20 .category14 .area,.epoch.category20 .category14 .dot{fill:#f7b6d2;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category14 path{fill:#f7b6d2;}.epoch.category20 .bar.category14{fill:#f7b6d2;}.epoch.category20 div.ref.category15{background-color:#7f7f7f;}.epoch.category20 .category15 .line{stroke:#7f7f7f;}.epoch.category20 .category15 .area,.epoch.category20 .category15 .dot{fill:#7f7f7f;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category15 path{fill:#7f7f7f;}.epoch.category20 .bar.category15{fill:#7f7f7f;}.epoch.category20 div.ref.category16{background-color:#c7c7c7;}.epoch.category20 .category16 .line{stroke:#c7c7c7;}.epoch.category20 .category16 .area,.epoch.category20 .category16 .dot{fill:#c7c7c7;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category16 path{fill:#c7c7c7;}.epoch.category20 .bar.category16{fill:#c7c7c7;}.epoch.category20 div.ref.category17{background-color:#bcbd22;}.epoch.category20 .category17 .line{stroke:#bcbd22;}.epoch.category20 .category17 .area,.epoch.category20 .category17 .dot{fill:#bcbd22;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category17 path{fill:#bcbd22;}.epoch.category20 .bar.category17{fill:#bcbd22;}.epoch.category20 div.ref.category18{background-color:#dbdb8d;}.epoch.category20 .category18 .line{stroke:#dbdb8d;}.epoch.category20 .category18 .area,.epoch.category20 .category18 .dot{fill:#dbdb8d;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category18 path{fill:#dbdb8d;}.epoch.category20 .bar.category18{fill:#dbdb8d;}.epoch.category20 div.ref.category19{background-color:#17becf;}.epoch.category20 .category19 .line{stroke:#17becf;}.epoch.category20 .category19 .area,.epoch.category20 .category19 .dot{fill:#17becf;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category19 path{fill:#17becf;}.epoch.category20 .bar.category19{fill:#17becf;}.epoch.category20 div.ref.category20{background-color:#9edae5;}.epoch.category20 .category20 .line{stroke:#9edae5;}.epoch.category20 .category20 .area,.epoch.category20 .category20 .dot{fill:#9edae5;stroke:rgba(0, 0, 0, 0);}.epoch.category20 .arc.category20 path{fill:#9edae5;}.epoch.category20 .bar.category20{fill:#9edae5;}.epoch.category20b div.ref.category1{background-color:#393b79;}.epoch.category20b .category1 .line{stroke:#393b79;}.epoch.category20b .category1 .area,.epoch.category20b .category1 .dot{fill:#393b79;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category1 path{fill:#393b79;}.epoch.category20b .bar.category1{fill:#393b79;}.epoch.category20b div.ref.category2{background-color:#5254a3;}.epoch.category20b .category2 .line{stroke:#5254a3;}.epoch.category20b .category2 .area,.epoch.category20b .category2 .dot{fill:#5254a3;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category2 path{fill:#5254a3;}.epoch.category20b .bar.category2{fill:#5254a3;}.epoch.category20b div.ref.category3{background-color:#6b6ecf;}.epoch.category20b .category3 .line{stroke:#6b6ecf;}.epoch.category20b .category3 .area,.epoch.category20b .category3 .dot{fill:#6b6ecf;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category3 path{fill:#6b6ecf;}.epoch.category20b .bar.category3{fill:#6b6ecf;}.epoch.category20b div.ref.category4{background-color:#9c9ede;}.epoch.category20b .category4 .line{stroke:#9c9ede;}.epoch.category20b .category4 .area,.epoch.category20b .category4 .dot{fill:#9c9ede;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category4 path{fill:#9c9ede;}.epoch.category20b .bar.category4{fill:#9c9ede;}.epoch.category20b div.ref.category5{background-color:#637939;}.epoch.category20b .category5 .line{stroke:#637939;}.epoch.category20b .category5 .area,.epoch.category20b .category5 .dot{fill:#637939;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category5 path{fill:#637939;}.epoch.category20b .bar.category5{fill:#637939;}.epoch.category20b div.ref.category6{background-color:#8ca252;}.epoch.category20b .category6 .line{stroke:#8ca252;}.epoch.category20b .category6 .area,.epoch.category20b .category6 .dot{fill:#8ca252;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category6 path{fill:#8ca252;}.epoch.category20b .bar.category6{fill:#8ca252;}.epoch.category20b div.ref.category7{background-color:#b5cf6b;}.epoch.category20b .category7 .line{stroke:#b5cf6b;}.epoch.category20b .category7 .area,.epoch.category20b .category7 .dot{fill:#b5cf6b;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category7 path{fill:#b5cf6b;}.epoch.category20b .bar.category7{fill:#b5cf6b;}.epoch.category20b div.ref.category8{background-color:#cedb9c;}.epoch.category20b .category8 .line{stroke:#cedb9c;}.epoch.category20b .category8 .area,.epoch.category20b .category8 .dot{fill:#cedb9c;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category8 path{fill:#cedb9c;}.epoch.category20b .bar.category8{fill:#cedb9c;}.epoch.category20b div.ref.category9{background-color:#8c6d31;}.epoch.category20b .category9 .line{stroke:#8c6d31;}.epoch.category20b .category9 .area,.epoch.category20b .category9 .dot{fill:#8c6d31;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category9 path{fill:#8c6d31;}.epoch.category20b .bar.category9{fill:#8c6d31;}.epoch.category20b div.ref.category10{background-color:#bd9e39;}.epoch.category20b .category10 .line{stroke:#bd9e39;}.epoch.category20b .category10 .area,.epoch.category20b .category10 .dot{fill:#bd9e39;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category10 path{fill:#bd9e39;}.epoch.category20b .bar.category10{fill:#bd9e39;}.epoch.category20b div.ref.category11{background-color:#e7ba52;}.epoch.category20b .category11 .line{stroke:#e7ba52;}.epoch.category20b .category11 .area,.epoch.category20b .category11 .dot{fill:#e7ba52;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category11 path{fill:#e7ba52;}.epoch.category20b .bar.category11{fill:#e7ba52;}.epoch.category20b div.ref.category12{background-color:#e7cb94;}.epoch.category20b .category12 .line{stroke:#e7cb94;}.epoch.category20b .category12 .area,.epoch.category20b .category12 .dot{fill:#e7cb94;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category12 path{fill:#e7cb94;}.epoch.category20b .bar.category12{fill:#e7cb94;}.epoch.category20b div.ref.category13{background-color:#843c39;}.epoch.category20b .category13 .line{stroke:#843c39;}.epoch.category20b .category13 .area,.epoch.category20b .category13 .dot{fill:#843c39;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category13 path{fill:#843c39;}.epoch.category20b .bar.category13{fill:#843c39;}.epoch.category20b div.ref.category14{background-color:#ad494a;}.epoch.category20b .category14 .line{stroke:#ad494a;}.epoch.category20b .category14 .area,.epoch.category20b .category14 .dot{fill:#ad494a;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category14 path{fill:#ad494a;}.epoch.category20b .bar.category14{fill:#ad494a;}.epoch.category20b div.ref.category15{background-color:#d6616b;}.epoch.category20b .category15 .line{stroke:#d6616b;}.epoch.category20b .category15 .area,.epoch.category20b .category15 .dot{fill:#d6616b;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category15 path{fill:#d6616b;}.epoch.category20b .bar.category15{fill:#d6616b;}.epoch.category20b div.ref.category16{background-color:#e7969c;}.epoch.category20b .category16 .line{stroke:#e7969c;}.epoch.category20b .category16 .area,.epoch.category20b .category16 .dot{fill:#e7969c;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category16 path{fill:#e7969c;}.epoch.category20b .bar.category16{fill:#e7969c;}.epoch.category20b div.ref.category17{background-color:#7b4173;}.epoch.category20b .category17 .line{stroke:#7b4173;}.epoch.category20b .category17 .area,.epoch.category20b .category17 .dot{fill:#7b4173;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category17 path{fill:#7b4173;}.epoch.category20b .bar.category17{fill:#7b4173;}.epoch.category20b div.ref.category18{background-color:#a55194;}.epoch.category20b .category18 .line{stroke:#a55194;}.epoch.category20b .category18 .area,.epoch.category20b .category18 .dot{fill:#a55194;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category18 path{fill:#a55194;}.epoch.category20b .bar.category18{fill:#a55194;}.epoch.category20b div.ref.category19{background-color:#ce6dbd;}.epoch.category20b .category19 .line{stroke:#ce6dbd;}.epoch.category20b .category19 .area,.epoch.category20b .category19 .dot{fill:#ce6dbd;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category19 path{fill:#ce6dbd;}.epoch.category20b .bar.category19{fill:#ce6dbd;}.epoch.category20b div.ref.category20{background-color:#de9ed6;}.epoch.category20b .category20 .line{stroke:#de9ed6;}.epoch.category20b .category20 .area,.epoch.category20b .category20 .dot{fill:#de9ed6;stroke:rgba(0, 0, 0, 0);}.epoch.category20b .arc.category20 path{fill:#de9ed6;}.epoch.category20b .bar.category20{fill:#de9ed6;}.epoch.category20c div.ref.category1{background-color:#3182bd;}.epoch.category20c .category1 .line{stroke:#3182bd;}.epoch.category20c .category1 .area,.epoch.category20c .category1 .dot{fill:#3182bd;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category1 path{fill:#3182bd;}.epoch.category20c .bar.category1{fill:#3182bd;}.epoch.category20c div.ref.category2{background-color:#6baed6;}.epoch.category20c .category2 .line{stroke:#6baed6;}.epoch.category20c .category2 .area,.epoch.category20c .category2 .dot{fill:#6baed6;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category2 path{fill:#6baed6;}.epoch.category20c .bar.category2{fill:#6baed6;}.epoch.category20c div.ref.category3{background-color:#9ecae1;}.epoch.category20c .category3 .line{stroke:#9ecae1;}.epoch.category20c .category3 .area,.epoch.category20c .category3 .dot{fill:#9ecae1;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category3 path{fill:#9ecae1;}.epoch.category20c .bar.category3{fill:#9ecae1;}.epoch.category20c div.ref.category4{background-color:#c6dbef;}.epoch.category20c .category4 .line{stroke:#c6dbef;}.epoch.category20c .category4 .area,.epoch.category20c .category4 .dot{fill:#c6dbef;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category4 path{fill:#c6dbef;}.epoch.category20c .bar.category4{fill:#c6dbef;}.epoch.category20c div.ref.category5{background-color:#e6550d;}.epoch.category20c .category5 .line{stroke:#e6550d;}.epoch.category20c .category5 .area,.epoch.category20c .category5 .dot{fill:#e6550d;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category5 path{fill:#e6550d;}.epoch.category20c .bar.category5{fill:#e6550d;}.epoch.category20c div.ref.category6{background-color:#fd8d3c;}.epoch.category20c .category6 .line{stroke:#fd8d3c;}.epoch.category20c .category6 .area,.epoch.category20c .category6 .dot{fill:#fd8d3c;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category6 path{fill:#fd8d3c;}.epoch.category20c .bar.category6{fill:#fd8d3c;}.epoch.category20c div.ref.category7{background-color:#fdae6b;}.epoch.category20c .category7 .line{stroke:#fdae6b;}.epoch.category20c .category7 .area,.epoch.category20c .category7 .dot{fill:#fdae6b;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category7 path{fill:#fdae6b;}.epoch.category20c .bar.category7{fill:#fdae6b;}.epoch.category20c div.ref.category8{background-color:#fdd0a2;}.epoch.category20c .category8 .line{stroke:#fdd0a2;}.epoch.category20c .category8 .area,.epoch.category20c .category8 .dot{fill:#fdd0a2;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category8 path{fill:#fdd0a2;}.epoch.category20c .bar.category8{fill:#fdd0a2;}.epoch.category20c div.ref.category9{background-color:#31a354;}.epoch.category20c .category9 .line{stroke:#31a354;}.epoch.category20c .category9 .area,.epoch.category20c .category9 .dot{fill:#31a354;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category9 path{fill:#31a354;}.epoch.category20c .bar.category9{fill:#31a354;}.epoch.category20c div.ref.category10{background-color:#74c476;}.epoch.category20c .category10 .line{stroke:#74c476;}.epoch.category20c .category10 .area,.epoch.category20c .category10 .dot{fill:#74c476;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category10 path{fill:#74c476;}.epoch.category20c .bar.category10{fill:#74c476;}.epoch.category20c div.ref.category11{background-color:#a1d99b;}.epoch.category20c .category11 .line{stroke:#a1d99b;}.epoch.category20c .category11 .area,.epoch.category20c .category11 .dot{fill:#a1d99b;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category11 path{fill:#a1d99b;}.epoch.category20c .bar.category11{fill:#a1d99b;}.epoch.category20c div.ref.category12{background-color:#c7e9c0;}.epoch.category20c .category12 .line{stroke:#c7e9c0;}.epoch.category20c .category12 .area,.epoch.category20c .category12 .dot{fill:#c7e9c0;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category12 path{fill:#c7e9c0;}.epoch.category20c .bar.category12{fill:#c7e9c0;}.epoch.category20c div.ref.category13{background-color:#756bb1;}.epoch.category20c .category13 .line{stroke:#756bb1;}.epoch.category20c .category13 .area,.epoch.category20c .category13 .dot{fill:#756bb1;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category13 path{fill:#756bb1;}.epoch.category20c .bar.category13{fill:#756bb1;}.epoch.category20c div.ref.category14{background-color:#9e9ac8;}.epoch.category20c .category14 .line{stroke:#9e9ac8;}.epoch.category20c .category14 .area,.epoch.category20c .category14 .dot{fill:#9e9ac8;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category14 path{fill:#9e9ac8;}.epoch.category20c .bar.category14{fill:#9e9ac8;}.epoch.category20c div.ref.category15{background-color:#bcbddc;}.epoch.category20c .category15 .line{stroke:#bcbddc;}.epoch.category20c .category15 .area,.epoch.category20c .category15 .dot{fill:#bcbddc;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category15 path{fill:#bcbddc;}.epoch.category20c .bar.category15{fill:#bcbddc;}.epoch.category20c div.ref.category16{background-color:#dadaeb;}.epoch.category20c .category16 .line{stroke:#dadaeb;}.epoch.category20c .category16 .area,.epoch.category20c .category16 .dot{fill:#dadaeb;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category16 path{fill:#dadaeb;}.epoch.category20c .bar.category16{fill:#dadaeb;}.epoch.category20c div.ref.category17{background-color:#636363;}.epoch.category20c .category17 .line{stroke:#636363;}.epoch.category20c .category17 .area,.epoch.category20c .category17 .dot{fill:#636363;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category17 path{fill:#636363;}.epoch.category20c .bar.category17{fill:#636363;}.epoch.category20c div.ref.category18{background-color:#969696;}.epoch.category20c .category18 .line{stroke:#969696;}.epoch.category20c .category18 .area,.epoch.category20c .category18 .dot{fill:#969696;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category18 path{fill:#969696;}.epoch.category20c .bar.category18{fill:#969696;}.epoch.category20c div.ref.category19{background-color:#bdbdbd;}.epoch.category20c .category19 .line{stroke:#bdbdbd;}.epoch.category20c .category19 .area,.epoch.category20c .category19 .dot{fill:#bdbdbd;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category19 path{fill:#bdbdbd;}.epoch.category20c .bar.category19{fill:#bdbdbd;}.epoch.category20c div.ref.category20{background-color:#d9d9d9;}.epoch.category20c .category20 .line{stroke:#d9d9d9;}.epoch.category20c .category20 .area,.epoch.category20c .category20 .dot{fill:#d9d9d9;stroke:rgba(0, 0, 0, 0);}.epoch.category20c .arc.category20 path{fill:#d9d9d9;}.epoch.category20c .bar.category20{fill:#d9d9d9;}.epoch .category1 .bucket,.epoch.heatmap5 .category1 .bucket{fill:#1f77b4;}.epoch .category2 .bucket,.epoch.heatmap5 .category2 .bucket{fill:#2ca02c;}.epoch .category3 .bucket,.epoch.heatmap5 .category3 .bucket{fill:#d62728;}.epoch .category4 .bucket,.epoch.heatmap5 .category4 .bucket{fill:#8c564b;}.epoch .category5 .bucket,.epoch.heatmap5 .category5 .bucket{fill:#7f7f7f;}.epoch-theme-dark .epoch .axis path,.epoch-theme-dark .epoch .axis line{stroke:#d0d0d0;}.epoch-theme-dark .epoch .axis .tick text{fill:#d0d0d0;}.epoch-theme-dark .arc.pie{stroke:#333;}.epoch-theme-dark .arc.pie text{fill:#333;}.epoch-theme-dark .epoch .gauge-labels .value{fill:#BBB;}.epoch-theme-dark .epoch .gauge .arc.outer{stroke:#999;}.epoch-theme-dark .epoch .gauge .arc.inner{stroke:#AAA;}.epoch-theme-dark .epoch .gauge .tick{stroke:#AAA;}.epoch-theme-dark .epoch .gauge .needle{fill:#F3DE88;}.epoch-theme-dark .epoch .gauge .needle-base{fill:#999;}.epoch-theme-dark .epoch div.ref.category1,.epoch-theme-dark .epoch.category10 div.ref.category1{background-color:#909CFF;}.epoch-theme-dark .epoch .category1 .line,.epoch-theme-dark .epoch.category10 .category1 .line{stroke:#909CFF;}.epoch-theme-dark .epoch .category1 .area,.epoch-theme-dark .epoch .category1 .dot,.epoch-theme-dark .epoch.category10 .category1 .area,.epoch-theme-dark .epoch.category10 .category1 .dot{fill:#909CFF;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category1 path,.epoch-theme-dark .epoch.category10 .arc.category1 path{fill:#909CFF;}.epoch-theme-dark .epoch .bar.category1,.epoch-theme-dark .epoch.category10 .bar.category1{fill:#909CFF;}.epoch-theme-dark .epoch div.ref.category2,.epoch-theme-dark .epoch.category10 div.ref.category2{background-color:#FFAC89;}.epoch-theme-dark .epoch .category2 .line,.epoch-theme-dark .epoch.category10 .category2 .line{stroke:#FFAC89;}.epoch-theme-dark .epoch .category2 .area,.epoch-theme-dark .epoch .category2 .dot,.epoch-theme-dark .epoch.category10 .category2 .area,.epoch-theme-dark .epoch.category10 .category2 .dot{fill:#FFAC89;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category2 path,.epoch-theme-dark .epoch.category10 .arc.category2 path{fill:#FFAC89;}.epoch-theme-dark .epoch .bar.category2,.epoch-theme-dark .epoch.category10 .bar.category2{fill:#FFAC89;}.epoch-theme-dark .epoch div.ref.category3,.epoch-theme-dark .epoch.category10 div.ref.category3{background-color:#E889E8;}.epoch-theme-dark .epoch .category3 .line,.epoch-theme-dark .epoch.category10 .category3 .line{stroke:#E889E8;}.epoch-theme-dark .epoch .category3 .area,.epoch-theme-dark .epoch .category3 .dot,.epoch-theme-dark .epoch.category10 .category3 .area,.epoch-theme-dark .epoch.category10 .category3 .dot{fill:#E889E8;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category3 path,.epoch-theme-dark .epoch.category10 .arc.category3 path{fill:#E889E8;}.epoch-theme-dark .epoch .bar.category3,.epoch-theme-dark .epoch.category10 .bar.category3{fill:#E889E8;}.epoch-theme-dark .epoch div.ref.category4,.epoch-theme-dark .epoch.category10 div.ref.category4{background-color:#78E8D3;}.epoch-theme-dark .epoch .category4 .line,.epoch-theme-dark .epoch.category10 .category4 .line{stroke:#78E8D3;}.epoch-theme-dark .epoch .category4 .area,.epoch-theme-dark .epoch .category4 .dot,.epoch-theme-dark .epoch.category10 .category4 .area,.epoch-theme-dark .epoch.category10 .category4 .dot{fill:#78E8D3;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category4 path,.epoch-theme-dark .epoch.category10 .arc.category4 path{fill:#78E8D3;}.epoch-theme-dark .epoch .bar.category4,.epoch-theme-dark .epoch.category10 .bar.category4{fill:#78E8D3;}.epoch-theme-dark .epoch div.ref.category5,.epoch-theme-dark .epoch.category10 div.ref.category5{background-color:#C2FF97;}.epoch-theme-dark .epoch .category5 .line,.epoch-theme-dark .epoch.category10 .category5 .line{stroke:#C2FF97;}.epoch-theme-dark .epoch .category5 .area,.epoch-theme-dark .epoch .category5 .dot,.epoch-theme-dark .epoch.category10 .category5 .area,.epoch-theme-dark .epoch.category10 .category5 .dot{fill:#C2FF97;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category5 path,.epoch-theme-dark .epoch.category10 .arc.category5 path{fill:#C2FF97;}.epoch-theme-dark .epoch .bar.category5,.epoch-theme-dark .epoch.category10 .bar.category5{fill:#C2FF97;}.epoch-theme-dark .epoch div.ref.category6,.epoch-theme-dark .epoch.category10 div.ref.category6{background-color:#B7BCD1;}.epoch-theme-dark .epoch .category6 .line,.epoch-theme-dark .epoch.category10 .category6 .line{stroke:#B7BCD1;}.epoch-theme-dark .epoch .category6 .area,.epoch-theme-dark .epoch .category6 .dot,.epoch-theme-dark .epoch.category10 .category6 .area,.epoch-theme-dark .epoch.category10 .category6 .dot{fill:#B7BCD1;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category6 path,.epoch-theme-dark .epoch.category10 .arc.category6 path{fill:#B7BCD1;}.epoch-theme-dark .epoch .bar.category6,.epoch-theme-dark .epoch.category10 .bar.category6{fill:#B7BCD1;}.epoch-theme-dark .epoch div.ref.category7,.epoch-theme-dark .epoch.category10 div.ref.category7{background-color:#FF857F;}.epoch-theme-dark .epoch .category7 .line,.epoch-theme-dark .epoch.category10 .category7 .line{stroke:#FF857F;}.epoch-theme-dark .epoch .category7 .area,.epoch-theme-dark .epoch .category7 .dot,.epoch-theme-dark .epoch.category10 .category7 .area,.epoch-theme-dark .epoch.category10 .category7 .dot{fill:#FF857F;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category7 path,.epoch-theme-dark .epoch.category10 .arc.category7 path{fill:#FF857F;}.epoch-theme-dark .epoch .bar.category7,.epoch-theme-dark .epoch.category10 .bar.category7{fill:#FF857F;}.epoch-theme-dark .epoch div.ref.category8,.epoch-theme-dark .epoch.category10 div.ref.category8{background-color:#F3DE88;}.epoch-theme-dark .epoch .category8 .line,.epoch-theme-dark .epoch.category10 .category8 .line{stroke:#F3DE88;}.epoch-theme-dark .epoch .category8 .area,.epoch-theme-dark .epoch .category8 .dot,.epoch-theme-dark .epoch.category10 .category8 .area,.epoch-theme-dark .epoch.category10 .category8 .dot{fill:#F3DE88;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category8 path,.epoch-theme-dark .epoch.category10 .arc.category8 path{fill:#F3DE88;}.epoch-theme-dark .epoch .bar.category8,.epoch-theme-dark .epoch.category10 .bar.category8{fill:#F3DE88;}.epoch-theme-dark .epoch div.ref.category9,.epoch-theme-dark .epoch.category10 div.ref.category9{background-color:#C9935E;}.epoch-theme-dark .epoch .category9 .line,.epoch-theme-dark .epoch.category10 .category9 .line{stroke:#C9935E;}.epoch-theme-dark .epoch .category9 .area,.epoch-theme-dark .epoch .category9 .dot,.epoch-theme-dark .epoch.category10 .category9 .area,.epoch-theme-dark .epoch.category10 .category9 .dot{fill:#C9935E;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category9 path,.epoch-theme-dark .epoch.category10 .arc.category9 path{fill:#C9935E;}.epoch-theme-dark .epoch .bar.category9,.epoch-theme-dark .epoch.category10 .bar.category9{fill:#C9935E;}.epoch-theme-dark .epoch div.ref.category10,.epoch-theme-dark .epoch.category10 div.ref.category10{background-color:#A488FF;}.epoch-theme-dark .epoch .category10 .line,.epoch-theme-dark .epoch.category10 .category10 .line{stroke:#A488FF;}.epoch-theme-dark .epoch .category10 .area,.epoch-theme-dark .epoch .category10 .dot,.epoch-theme-dark .epoch.category10 .category10 .area,.epoch-theme-dark .epoch.category10 .category10 .dot{fill:#A488FF;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch .arc.category10 path,.epoch-theme-dark .epoch.category10 .arc.category10 path{fill:#A488FF;}.epoch-theme-dark .epoch .bar.category10,.epoch-theme-dark .epoch.category10 .bar.category10{fill:#A488FF;}.epoch-theme-dark .epoch.category20 div.ref.category1{background-color:#909CFF;}.epoch-theme-dark .epoch.category20 .category1 .line{stroke:#909CFF;}.epoch-theme-dark .epoch.category20 .category1 .area,.epoch-theme-dark .epoch.category20 .category1 .dot{fill:#909CFF;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category1 path{fill:#909CFF;}.epoch-theme-dark .epoch.category20 .bar.category1{fill:#909CFF;}.epoch-theme-dark .epoch.category20 div.ref.category2{background-color:#626AAD;}.epoch-theme-dark .epoch.category20 .category2 .line{stroke:#626AAD;}.epoch-theme-dark .epoch.category20 .category2 .area,.epoch-theme-dark .epoch.category20 .category2 .dot{fill:#626AAD;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category2 path{fill:#626AAD;}.epoch-theme-dark .epoch.category20 .bar.category2{fill:#626AAD;}.epoch-theme-dark .epoch.category20 div.ref.category3{background-color:#FFAC89;}.epoch-theme-dark .epoch.category20 .category3 .line{stroke:#FFAC89;}.epoch-theme-dark .epoch.category20 .category3 .area,.epoch-theme-dark .epoch.category20 .category3 .dot{fill:#FFAC89;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category3 path{fill:#FFAC89;}.epoch-theme-dark .epoch.category20 .bar.category3{fill:#FFAC89;}.epoch-theme-dark .epoch.category20 div.ref.category4{background-color:#BD7F66;}.epoch-theme-dark .epoch.category20 .category4 .line{stroke:#BD7F66;}.epoch-theme-dark .epoch.category20 .category4 .area,.epoch-theme-dark .epoch.category20 .category4 .dot{fill:#BD7F66;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category4 path{fill:#BD7F66;}.epoch-theme-dark .epoch.category20 .bar.category4{fill:#BD7F66;}.epoch-theme-dark .epoch.category20 div.ref.category5{background-color:#E889E8;}.epoch-theme-dark .epoch.category20 .category5 .line{stroke:#E889E8;}.epoch-theme-dark .epoch.category20 .category5 .area,.epoch-theme-dark .epoch.category20 .category5 .dot{fill:#E889E8;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category5 path{fill:#E889E8;}.epoch-theme-dark .epoch.category20 .bar.category5{fill:#E889E8;}.epoch-theme-dark .epoch.category20 div.ref.category6{background-color:#995A99;}.epoch-theme-dark .epoch.category20 .category6 .line{stroke:#995A99;}.epoch-theme-dark .epoch.category20 .category6 .area,.epoch-theme-dark .epoch.category20 .category6 .dot{fill:#995A99;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category6 path{fill:#995A99;}.epoch-theme-dark .epoch.category20 .bar.category6{fill:#995A99;}.epoch-theme-dark .epoch.category20 div.ref.category7{background-color:#78E8D3;}.epoch-theme-dark .epoch.category20 .category7 .line{stroke:#78E8D3;}.epoch-theme-dark .epoch.category20 .category7 .area,.epoch-theme-dark .epoch.category20 .category7 .dot{fill:#78E8D3;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category7 path{fill:#78E8D3;}.epoch-theme-dark .epoch.category20 .bar.category7{fill:#78E8D3;}.epoch-theme-dark .epoch.category20 div.ref.category8{background-color:#4F998C;}.epoch-theme-dark .epoch.category20 .category8 .line{stroke:#4F998C;}.epoch-theme-dark .epoch.category20 .category8 .area,.epoch-theme-dark .epoch.category20 .category8 .dot{fill:#4F998C;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category8 path{fill:#4F998C;}.epoch-theme-dark .epoch.category20 .bar.category8{fill:#4F998C;}.epoch-theme-dark .epoch.category20 div.ref.category9{background-color:#C2FF97;}.epoch-theme-dark .epoch.category20 .category9 .line{stroke:#C2FF97;}.epoch-theme-dark .epoch.category20 .category9 .area,.epoch-theme-dark .epoch.category20 .category9 .dot{fill:#C2FF97;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category9 path{fill:#C2FF97;}.epoch-theme-dark .epoch.category20 .bar.category9{fill:#C2FF97;}.epoch-theme-dark .epoch.category20 div.ref.category10{background-color:#789E5E;}.epoch-theme-dark .epoch.category20 .category10 .line{stroke:#789E5E;}.epoch-theme-dark .epoch.category20 .category10 .area,.epoch-theme-dark .epoch.category20 .category10 .dot{fill:#789E5E;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category10 path{fill:#789E5E;}.epoch-theme-dark .epoch.category20 .bar.category10{fill:#789E5E;}.epoch-theme-dark .epoch.category20 div.ref.category11{background-color:#B7BCD1;}.epoch-theme-dark .epoch.category20 .category11 .line{stroke:#B7BCD1;}.epoch-theme-dark .epoch.category20 .category11 .area,.epoch-theme-dark .epoch.category20 .category11 .dot{fill:#B7BCD1;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category11 path{fill:#B7BCD1;}.epoch-theme-dark .epoch.category20 .bar.category11{fill:#B7BCD1;}.epoch-theme-dark .epoch.category20 div.ref.category12{background-color:#7F8391;}.epoch-theme-dark .epoch.category20 .category12 .line{stroke:#7F8391;}.epoch-theme-dark .epoch.category20 .category12 .area,.epoch-theme-dark .epoch.category20 .category12 .dot{fill:#7F8391;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category12 path{fill:#7F8391;}.epoch-theme-dark .epoch.category20 .bar.category12{fill:#7F8391;}.epoch-theme-dark .epoch.category20 div.ref.category13{background-color:#CCB889;}.epoch-theme-dark .epoch.category20 .category13 .line{stroke:#CCB889;}.epoch-theme-dark .epoch.category20 .category13 .area,.epoch-theme-dark .epoch.category20 .category13 .dot{fill:#CCB889;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category13 path{fill:#CCB889;}.epoch-theme-dark .epoch.category20 .bar.category13{fill:#CCB889;}.epoch-theme-dark .epoch.category20 div.ref.category14{background-color:#A1906B;}.epoch-theme-dark .epoch.category20 .category14 .line{stroke:#A1906B;}.epoch-theme-dark .epoch.category20 .category14 .area,.epoch-theme-dark .epoch.category20 .category14 .dot{fill:#A1906B;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category14 path{fill:#A1906B;}.epoch-theme-dark .epoch.category20 .bar.category14{fill:#A1906B;}.epoch-theme-dark .epoch.category20 div.ref.category15{background-color:#F3DE88;}.epoch-theme-dark .epoch.category20 .category15 .line{stroke:#F3DE88;}.epoch-theme-dark .epoch.category20 .category15 .area,.epoch-theme-dark .epoch.category20 .category15 .dot{fill:#F3DE88;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category15 path{fill:#F3DE88;}.epoch-theme-dark .epoch.category20 .bar.category15{fill:#F3DE88;}.epoch-theme-dark .epoch.category20 div.ref.category16{background-color:#A89A5E;}.epoch-theme-dark .epoch.category20 .category16 .line{stroke:#A89A5E;}.epoch-theme-dark .epoch.category20 .category16 .area,.epoch-theme-dark .epoch.category20 .category16 .dot{fill:#A89A5E;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category16 path{fill:#A89A5E;}.epoch-theme-dark .epoch.category20 .bar.category16{fill:#A89A5E;}.epoch-theme-dark .epoch.category20 div.ref.category17{background-color:#FF857F;}.epoch-theme-dark .epoch.category20 .category17 .line{stroke:#FF857F;}.epoch-theme-dark .epoch.category20 .category17 .area,.epoch-theme-dark .epoch.category20 .category17 .dot{fill:#FF857F;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category17 path{fill:#FF857F;}.epoch-theme-dark .epoch.category20 .bar.category17{fill:#FF857F;}.epoch-theme-dark .epoch.category20 div.ref.category18{background-color:#BA615D;}.epoch-theme-dark .epoch.category20 .category18 .line{stroke:#BA615D;}.epoch-theme-dark .epoch.category20 .category18 .area,.epoch-theme-dark .epoch.category20 .category18 .dot{fill:#BA615D;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category18 path{fill:#BA615D;}.epoch-theme-dark .epoch.category20 .bar.category18{fill:#BA615D;}.epoch-theme-dark .epoch.category20 div.ref.category19{background-color:#A488FF;}.epoch-theme-dark .epoch.category20 .category19 .line{stroke:#A488FF;}.epoch-theme-dark .epoch.category20 .category19 .area,.epoch-theme-dark .epoch.category20 .category19 .dot{fill:#A488FF;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category19 path{fill:#A488FF;}.epoch-theme-dark .epoch.category20 .bar.category19{fill:#A488FF;}.epoch-theme-dark .epoch.category20 div.ref.category20{background-color:#7662B8;}.epoch-theme-dark .epoch.category20 .category20 .line{stroke:#7662B8;}.epoch-theme-dark .epoch.category20 .category20 .area,.epoch-theme-dark .epoch.category20 .category20 .dot{fill:#7662B8;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20 .arc.category20 path{fill:#7662B8;}.epoch-theme-dark .epoch.category20 .bar.category20{fill:#7662B8;}.epoch-theme-dark .epoch.category20b div.ref.category1{background-color:#909CFF;}.epoch-theme-dark .epoch.category20b .category1 .line{stroke:#909CFF;}.epoch-theme-dark .epoch.category20b .category1 .area,.epoch-theme-dark .epoch.category20b .category1 .dot{fill:#909CFF;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category1 path{fill:#909CFF;}.epoch-theme-dark .epoch.category20b .bar.category1{fill:#909CFF;}.epoch-theme-dark .epoch.category20b div.ref.category2{background-color:#7680D1;}.epoch-theme-dark .epoch.category20b .category2 .line{stroke:#7680D1;}.epoch-theme-dark .epoch.category20b .category2 .area,.epoch-theme-dark .epoch.category20b .category2 .dot{fill:#7680D1;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category2 path{fill:#7680D1;}.epoch-theme-dark .epoch.category20b .bar.category2{fill:#7680D1;}.epoch-theme-dark .epoch.category20b div.ref.category3{background-color:#656DB2;}.epoch-theme-dark .epoch.category20b .category3 .line{stroke:#656DB2;}.epoch-theme-dark .epoch.category20b .category3 .area,.epoch-theme-dark .epoch.category20b .category3 .dot{fill:#656DB2;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category3 path{fill:#656DB2;}.epoch-theme-dark .epoch.category20b .bar.category3{fill:#656DB2;}.epoch-theme-dark .epoch.category20b div.ref.category4{background-color:#525992;}.epoch-theme-dark .epoch.category20b .category4 .line{stroke:#525992;}.epoch-theme-dark .epoch.category20b .category4 .area,.epoch-theme-dark .epoch.category20b .category4 .dot{fill:#525992;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category4 path{fill:#525992;}.epoch-theme-dark .epoch.category20b .bar.category4{fill:#525992;}.epoch-theme-dark .epoch.category20b div.ref.category5{background-color:#FFAC89;}.epoch-theme-dark .epoch.category20b .category5 .line{stroke:#FFAC89;}.epoch-theme-dark .epoch.category20b .category5 .area,.epoch-theme-dark .epoch.category20b .category5 .dot{fill:#FFAC89;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category5 path{fill:#FFAC89;}.epoch-theme-dark .epoch.category20b .bar.category5{fill:#FFAC89;}.epoch-theme-dark .epoch.category20b div.ref.category6{background-color:#D18D71;}.epoch-theme-dark .epoch.category20b .category6 .line{stroke:#D18D71;}.epoch-theme-dark .epoch.category20b .category6 .area,.epoch-theme-dark .epoch.category20b .category6 .dot{fill:#D18D71;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category6 path{fill:#D18D71;}.epoch-theme-dark .epoch.category20b .bar.category6{fill:#D18D71;}.epoch-theme-dark .epoch.category20b div.ref.category7{background-color:#AB735C;}.epoch-theme-dark .epoch.category20b .category7 .line{stroke:#AB735C;}.epoch-theme-dark .epoch.category20b .category7 .area,.epoch-theme-dark .epoch.category20b .category7 .dot{fill:#AB735C;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category7 path{fill:#AB735C;}.epoch-theme-dark .epoch.category20b .bar.category7{fill:#AB735C;}.epoch-theme-dark .epoch.category20b div.ref.category8{background-color:#92624E;}.epoch-theme-dark .epoch.category20b .category8 .line{stroke:#92624E;}.epoch-theme-dark .epoch.category20b .category8 .area,.epoch-theme-dark .epoch.category20b .category8 .dot{fill:#92624E;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category8 path{fill:#92624E;}.epoch-theme-dark .epoch.category20b .bar.category8{fill:#92624E;}.epoch-theme-dark .epoch.category20b div.ref.category9{background-color:#E889E8;}.epoch-theme-dark .epoch.category20b .category9 .line{stroke:#E889E8;}.epoch-theme-dark .epoch.category20b .category9 .area,.epoch-theme-dark .epoch.category20b .category9 .dot{fill:#E889E8;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category9 path{fill:#E889E8;}.epoch-theme-dark .epoch.category20b .bar.category9{fill:#E889E8;}.epoch-theme-dark .epoch.category20b div.ref.category10{background-color:#BA6EBA;}.epoch-theme-dark .epoch.category20b .category10 .line{stroke:#BA6EBA;}.epoch-theme-dark .epoch.category20b .category10 .area,.epoch-theme-dark .epoch.category20b .category10 .dot{fill:#BA6EBA;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category10 path{fill:#BA6EBA;}.epoch-theme-dark .epoch.category20b .bar.category10{fill:#BA6EBA;}.epoch-theme-dark .epoch.category20b div.ref.category11{background-color:#9B5C9B;}.epoch-theme-dark .epoch.category20b .category11 .line{stroke:#9B5C9B;}.epoch-theme-dark .epoch.category20b .category11 .area,.epoch-theme-dark .epoch.category20b .category11 .dot{fill:#9B5C9B;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category11 path{fill:#9B5C9B;}.epoch-theme-dark .epoch.category20b .bar.category11{fill:#9B5C9B;}.epoch-theme-dark .epoch.category20b div.ref.category12{background-color:#7B487B;}.epoch-theme-dark .epoch.category20b .category12 .line{stroke:#7B487B;}.epoch-theme-dark .epoch.category20b .category12 .area,.epoch-theme-dark .epoch.category20b .category12 .dot{fill:#7B487B;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category12 path{fill:#7B487B;}.epoch-theme-dark .epoch.category20b .bar.category12{fill:#7B487B;}.epoch-theme-dark .epoch.category20b div.ref.category13{background-color:#78E8D3;}.epoch-theme-dark .epoch.category20b .category13 .line{stroke:#78E8D3;}.epoch-theme-dark .epoch.category20b .category13 .area,.epoch-theme-dark .epoch.category20b .category13 .dot{fill:#78E8D3;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category13 path{fill:#78E8D3;}.epoch-theme-dark .epoch.category20b .bar.category13{fill:#78E8D3;}.epoch-theme-dark .epoch.category20b div.ref.category14{background-color:#60BAAA;}.epoch-theme-dark .epoch.category20b .category14 .line{stroke:#60BAAA;}.epoch-theme-dark .epoch.category20b .category14 .area,.epoch-theme-dark .epoch.category20b .category14 .dot{fill:#60BAAA;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category14 path{fill:#60BAAA;}.epoch-theme-dark .epoch.category20b .bar.category14{fill:#60BAAA;}.epoch-theme-dark .epoch.category20b div.ref.category15{background-color:#509B8D;}.epoch-theme-dark .epoch.category20b .category15 .line{stroke:#509B8D;}.epoch-theme-dark .epoch.category20b .category15 .area,.epoch-theme-dark .epoch.category20b .category15 .dot{fill:#509B8D;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category15 path{fill:#509B8D;}.epoch-theme-dark .epoch.category20b .bar.category15{fill:#509B8D;}.epoch-theme-dark .epoch.category20b div.ref.category16{background-color:#3F7B70;}.epoch-theme-dark .epoch.category20b .category16 .line{stroke:#3F7B70;}.epoch-theme-dark .epoch.category20b .category16 .area,.epoch-theme-dark .epoch.category20b .category16 .dot{fill:#3F7B70;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category16 path{fill:#3F7B70;}.epoch-theme-dark .epoch.category20b .bar.category16{fill:#3F7B70;}.epoch-theme-dark .epoch.category20b div.ref.category17{background-color:#C2FF97;}.epoch-theme-dark .epoch.category20b .category17 .line{stroke:#C2FF97;}.epoch-theme-dark .epoch.category20b .category17 .area,.epoch-theme-dark .epoch.category20b .category17 .dot{fill:#C2FF97;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category17 path{fill:#C2FF97;}.epoch-theme-dark .epoch.category20b .bar.category17{fill:#C2FF97;}.epoch-theme-dark .epoch.category20b div.ref.category18{background-color:#9FD17C;}.epoch-theme-dark .epoch.category20b .category18 .line{stroke:#9FD17C;}.epoch-theme-dark .epoch.category20b .category18 .area,.epoch-theme-dark .epoch.category20b .category18 .dot{fill:#9FD17C;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category18 path{fill:#9FD17C;}.epoch-theme-dark .epoch.category20b .bar.category18{fill:#9FD17C;}.epoch-theme-dark .epoch.category20b div.ref.category19{background-color:#7DA361;}.epoch-theme-dark .epoch.category20b .category19 .line{stroke:#7DA361;}.epoch-theme-dark .epoch.category20b .category19 .area,.epoch-theme-dark .epoch.category20b .category19 .dot{fill:#7DA361;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category19 path{fill:#7DA361;}.epoch-theme-dark .epoch.category20b .bar.category19{fill:#7DA361;}.epoch-theme-dark .epoch.category20b div.ref.category20{background-color:#65854E;}.epoch-theme-dark .epoch.category20b .category20 .line{stroke:#65854E;}.epoch-theme-dark .epoch.category20b .category20 .area,.epoch-theme-dark .epoch.category20b .category20 .dot{fill:#65854E;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20b .arc.category20 path{fill:#65854E;}.epoch-theme-dark .epoch.category20b .bar.category20{fill:#65854E;}.epoch-theme-dark .epoch.category20c div.ref.category1{background-color:#B7BCD1;}.epoch-theme-dark .epoch.category20c .category1 .line{stroke:#B7BCD1;}.epoch-theme-dark .epoch.category20c .category1 .area,.epoch-theme-dark .epoch.category20c .category1 .dot{fill:#B7BCD1;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category1 path{fill:#B7BCD1;}.epoch-theme-dark .epoch.category20c .bar.category1{fill:#B7BCD1;}.epoch-theme-dark .epoch.category20c div.ref.category2{background-color:#979DAD;}.epoch-theme-dark .epoch.category20c .category2 .line{stroke:#979DAD;}.epoch-theme-dark .epoch.category20c .category2 .area,.epoch-theme-dark .epoch.category20c .category2 .dot{fill:#979DAD;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category2 path{fill:#979DAD;}.epoch-theme-dark .epoch.category20c .bar.category2{fill:#979DAD;}.epoch-theme-dark .epoch.category20c div.ref.category3{background-color:#6E717D;}.epoch-theme-dark .epoch.category20c .category3 .line{stroke:#6E717D;}.epoch-theme-dark .epoch.category20c .category3 .area,.epoch-theme-dark .epoch.category20c .category3 .dot{fill:#6E717D;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category3 path{fill:#6E717D;}.epoch-theme-dark .epoch.category20c .bar.category3{fill:#6E717D;}.epoch-theme-dark .epoch.category20c div.ref.category4{background-color:#595C66;}.epoch-theme-dark .epoch.category20c .category4 .line{stroke:#595C66;}.epoch-theme-dark .epoch.category20c .category4 .area,.epoch-theme-dark .epoch.category20c .category4 .dot{fill:#595C66;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category4 path{fill:#595C66;}.epoch-theme-dark .epoch.category20c .bar.category4{fill:#595C66;}.epoch-theme-dark .epoch.category20c div.ref.category5{background-color:#FF857F;}.epoch-theme-dark .epoch.category20c .category5 .line{stroke:#FF857F;}.epoch-theme-dark .epoch.category20c .category5 .area,.epoch-theme-dark .epoch.category20c .category5 .dot{fill:#FF857F;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category5 path{fill:#FF857F;}.epoch-theme-dark .epoch.category20c .bar.category5{fill:#FF857F;}.epoch-theme-dark .epoch.category20c div.ref.category6{background-color:#DE746E;}.epoch-theme-dark .epoch.category20c .category6 .line{stroke:#DE746E;}.epoch-theme-dark .epoch.category20c .category6 .area,.epoch-theme-dark .epoch.category20c .category6 .dot{fill:#DE746E;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category6 path{fill:#DE746E;}.epoch-theme-dark .epoch.category20c .bar.category6{fill:#DE746E;}.epoch-theme-dark .epoch.category20c div.ref.category7{background-color:#B55F5A;}.epoch-theme-dark .epoch.category20c .category7 .line{stroke:#B55F5A;}.epoch-theme-dark .epoch.category20c .category7 .area,.epoch-theme-dark .epoch.category20c .category7 .dot{fill:#B55F5A;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category7 path{fill:#B55F5A;}.epoch-theme-dark .epoch.category20c .bar.category7{fill:#B55F5A;}.epoch-theme-dark .epoch.category20c div.ref.category8{background-color:#964E4B;}.epoch-theme-dark .epoch.category20c .category8 .line{stroke:#964E4B;}.epoch-theme-dark .epoch.category20c .category8 .area,.epoch-theme-dark .epoch.category20c .category8 .dot{fill:#964E4B;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category8 path{fill:#964E4B;}.epoch-theme-dark .epoch.category20c .bar.category8{fill:#964E4B;}.epoch-theme-dark .epoch.category20c div.ref.category9{background-color:#F3DE88;}.epoch-theme-dark .epoch.category20c .category9 .line{stroke:#F3DE88;}.epoch-theme-dark .epoch.category20c .category9 .area,.epoch-theme-dark .epoch.category20c .category9 .dot{fill:#F3DE88;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category9 path{fill:#F3DE88;}.epoch-theme-dark .epoch.category20c .bar.category9{fill:#F3DE88;}.epoch-theme-dark .epoch.category20c div.ref.category10{background-color:#DBC87B;}.epoch-theme-dark .epoch.category20c .category10 .line{stroke:#DBC87B;}.epoch-theme-dark .epoch.category20c .category10 .area,.epoch-theme-dark .epoch.category20c .category10 .dot{fill:#DBC87B;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category10 path{fill:#DBC87B;}.epoch-theme-dark .epoch.category20c .bar.category10{fill:#DBC87B;}.epoch-theme-dark .epoch.category20c div.ref.category11{background-color:#BAAA68;}.epoch-theme-dark .epoch.category20c .category11 .line{stroke:#BAAA68;}.epoch-theme-dark .epoch.category20c .category11 .area,.epoch-theme-dark .epoch.category20c .category11 .dot{fill:#BAAA68;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category11 path{fill:#BAAA68;}.epoch-theme-dark .epoch.category20c .bar.category11{fill:#BAAA68;}.epoch-theme-dark .epoch.category20c div.ref.category12{background-color:#918551;}.epoch-theme-dark .epoch.category20c .category12 .line{stroke:#918551;}.epoch-theme-dark .epoch.category20c .category12 .area,.epoch-theme-dark .epoch.category20c .category12 .dot{fill:#918551;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category12 path{fill:#918551;}.epoch-theme-dark .epoch.category20c .bar.category12{fill:#918551;}.epoch-theme-dark .epoch.category20c div.ref.category13{background-color:#C9935E;}.epoch-theme-dark .epoch.category20c .category13 .line{stroke:#C9935E;}.epoch-theme-dark .epoch.category20c .category13 .area,.epoch-theme-dark .epoch.category20c .category13 .dot{fill:#C9935E;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category13 path{fill:#C9935E;}.epoch-theme-dark .epoch.category20c .bar.category13{fill:#C9935E;}.epoch-theme-dark .epoch.category20c div.ref.category14{background-color:#B58455;}.epoch-theme-dark .epoch.category20c .category14 .line{stroke:#B58455;}.epoch-theme-dark .epoch.category20c .category14 .area,.epoch-theme-dark .epoch.category20c .category14 .dot{fill:#B58455;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category14 path{fill:#B58455;}.epoch-theme-dark .epoch.category20c .bar.category14{fill:#B58455;}.epoch-theme-dark .epoch.category20c div.ref.category15{background-color:#997048;}.epoch-theme-dark .epoch.category20c .category15 .line{stroke:#997048;}.epoch-theme-dark .epoch.category20c .category15 .area,.epoch-theme-dark .epoch.category20c .category15 .dot{fill:#997048;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category15 path{fill:#997048;}.epoch-theme-dark .epoch.category20c .bar.category15{fill:#997048;}.epoch-theme-dark .epoch.category20c div.ref.category16{background-color:#735436;}.epoch-theme-dark .epoch.category20c .category16 .line{stroke:#735436;}.epoch-theme-dark .epoch.category20c .category16 .area,.epoch-theme-dark .epoch.category20c .category16 .dot{fill:#735436;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category16 path{fill:#735436;}.epoch-theme-dark .epoch.category20c .bar.category16{fill:#735436;}.epoch-theme-dark .epoch.category20c div.ref.category17{background-color:#A488FF;}.epoch-theme-dark .epoch.category20c .category17 .line{stroke:#A488FF;}.epoch-theme-dark .epoch.category20c .category17 .area,.epoch-theme-dark .epoch.category20c .category17 .dot{fill:#A488FF;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category17 path{fill:#A488FF;}.epoch-theme-dark .epoch.category20c .bar.category17{fill:#A488FF;}.epoch-theme-dark .epoch.category20c div.ref.category18{background-color:#8670D1;}.epoch-theme-dark .epoch.category20c .category18 .line{stroke:#8670D1;}.epoch-theme-dark .epoch.category20c .category18 .area,.epoch-theme-dark .epoch.category20c .category18 .dot{fill:#8670D1;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category18 path{fill:#8670D1;}.epoch-theme-dark .epoch.category20c .bar.category18{fill:#8670D1;}.epoch-theme-dark .epoch.category20c div.ref.category19{background-color:#705CAD;}.epoch-theme-dark .epoch.category20c .category19 .line{stroke:#705CAD;}.epoch-theme-dark .epoch.category20c .category19 .area,.epoch-theme-dark .epoch.category20c .category19 .dot{fill:#705CAD;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category19 path{fill:#705CAD;}.epoch-theme-dark .epoch.category20c .bar.category19{fill:#705CAD;}.epoch-theme-dark .epoch.category20c div.ref.category20{background-color:#52447F;}.epoch-theme-dark .epoch.category20c .category20 .line{stroke:#52447F;}.epoch-theme-dark .epoch.category20c .category20 .area,.epoch-theme-dark .epoch.category20c .category20 .dot{fill:#52447F;stroke:rgba(0, 0, 0, 0);}.epoch-theme-dark .epoch.category20c .arc.category20 path{fill:#52447F;}.epoch-theme-dark .epoch.category20c .bar.category20{fill:#52447F;} \ No newline at end of file diff --git a/examples/realtime-advanced/resources/static/epoch.min.js b/examples/realtime-advanced/resources/static/epoch.min.js deleted file mode 100644 index 0c654b8..0000000 --- a/examples/realtime-advanced/resources/static/epoch.min.js +++ /dev/null @@ -1,114 +0,0 @@ -(function(){var e;null==window.Epoch&&(window.Epoch={});null==(e=window.Epoch).Chart&&(e.Chart={});null==(e=window.Epoch).Time&&(e.Time={});null==(e=window.Epoch).Util&&(e.Util={});null==(e=window.Epoch).Formats&&(e.Formats={});Epoch.warn=function(g){return(console.warn||console.log)("Epoch Warning: "+g)};Epoch.exception=function(g){throw"Epoch Error: "+g;}}).call(this); -(function(){Epoch.TestContext=function(){function e(){var c,a,d;this._log=[];a=0;for(d=g.length;ac){if((c|0)!==c||d)c=c.toFixed(a);return c}f="KMGTPEZY".split("");for(h in f)if(k=f[h],b=Math.pow(10,3*((h|0)+1)),c>=b&&cc){if(0!==c%1||d)c=c.toFixed(a);return""+c+" B"}f="KB MB GB TB PB EB ZB YB".split(" ");for(h in f)if(k=f[h],b=Math.pow(1024,(h|0)+1),c>=b&&cf;k=1<=f?++a:--a)q.push(arguments[k]);return q}.apply(this,arguments);c=this._events[a];m=[];f=0;for(q=c.length;fthis.options.windowSize+1&&a.values.shift();b=[this._ticks[0],this._ticks[this._ticks.length-1]];a=b[0];b=b[1];null!=b&&b.enter&&(b.enter=!1,b.opacity=1);null!=a&&a.exit&&this._shiftTick();this.animation.frame=0;this.trigger("transition:end");if(0this.options.queueSize&&this._queue.splice(this.options.queueSize,this._queue.length-this.options.queueSize);if(this._queue.length===this.options.queueSize)return!1;this._queue.push(a.map(function(a){return function(b){return a._prepareEntry(b)}}(this)));this.trigger("push");if(!this.inTransition())return this._startTransition()}; -a.prototype._shift=function(){var a,b,c,d;this.trigger("before:shift");a=this._queue.shift();d=this.data;for(b in d)c=d[b],c.values.push(a[b]);this._updateTicks(a[0].time);this._transitionRangeAxes();return this.trigger("after:shift")};a.prototype._transitionRangeAxes=function(){this.hasAxis("left")&&this.svg.selectAll(".y.axis.left").transition().duration(500).ease("linear").call(this.leftAxis());if(this.hasAxis("right"))return this.svg.selectAll(".y.axis.right").transition().duration(500).ease("linear").call(this.rightAxis())}; -a.prototype._animate=function(){if(this.inTransition())return++this.animation.frame===this.animation.duration&&this._stopTransition(),this.draw(this.animation.frame*this.animation.delta()),this._updateTimeAxes()};a.prototype.y=function(){return d3.scale.linear().domain(this.extent(function(a){return a.y})).range([this.innerHeight(),0])};a.prototype.ySvg=function(){return d3.scale.linear().domain(this.extent(function(a){return a.y})).range([this.innerHeight()/this.pixelRatio,0])};a.prototype.w=function(){return this.innerWidth()/ -this.options.windowSize};a.prototype._updateTicks=function(a){if(this.hasAxis("top")||this.hasAxis("bottom"))if(++this._tickTimer%this.options.ticks.time||this._pushTick(this.options.windowSize,a,!0),!(0<=this._ticks[0].x-this.w()/this.pixelRatio))return this._ticks[0].exit=!0};a.prototype._pushTick=function(a,b,c,d){null==c&&(c=!1);null==d&&(d=!1);if(this.hasAxis("top")||this.hasAxis("bottom"))return b={time:b,x:a*(this.w()/this.pixelRatio)+this._offsetX(),opacity:c?0:1,enter:c?!0:!1,exit:!1},this.hasAxis("bottom")&& -(a=this.bottomAxis.append("g").attr("class","tick major").attr("transform","translate("+(b.x+1)+",0)").style("opacity",b.opacity),a.append("line").attr("y2",6),a.append("text").attr("text-anchor","middle").attr("dy",19).text(this.options.tickFormats.bottom(b.time)),b.bottomEl=a),this.hasAxis("top")&&(a=this.topAxis.append("g").attr("class","tick major").attr("transform","translate("+(b.x+1)+",0)").style("opacity",b.opacity),a.append("line").attr("y2",-6),a.append("text").attr("text-anchor","middle").attr("dy", --10).text(this.options.tickFormats.top(b.time)),b.topEl=a),d?this._ticks.unshift(b):this._ticks.push(b),b};a.prototype._shiftTick=function(){var a;if(0f;b=0<=f?++c:--c)k=0,e.push(function(){var a,c,d,f;d=this.data;f=[];a=0;for(c=d.length;ag;a=0<=g?++f:--f){b=e=k=0;for(m=this.data.length;0<=m?em;b=0<=m?++e:--e)k+=this.data[b].values[a].y;k>c&&(c=k)}return[0,c]};return a}(Epoch.Time.Plot)}).call(this); -(function(){var e={}.hasOwnProperty,g=function(c,a){function d(){this.constructor=c}for(var b in a)e.call(a,b)&&(c[b]=a[b]);d.prototype=a.prototype;c.prototype=new d;c.__super__=a.prototype;return c};Epoch.Time.Area=function(c){function a(){return a.__super__.constructor.apply(this,arguments)}g(a,c);a.prototype.setStyles=function(a){a=null!=a.className?this.getStyles("g."+a.className.replace(/\s/g,".")+" path.area"):this.getStyles("g path.area");this.ctx.fillStyle=a.fill;null!=a.stroke&&(this.ctx.strokeStyle= -a.stroke);if(null!=a["stroke-width"])return this.ctx.lineWidth=a["stroke-width"].replace("px","")};a.prototype._drawAreas=function(a){var b,c,k,f,e,g,m,l,n,p;null==a&&(a=0);g=[this.y(),this.w()];m=g[0];g=g[1];p=[];for(c=l=n=this.data.length-1;0>=n?0>=l:0<=l;c=0>=n?++l:--l){f=this.data[c];this.setStyles(f);this.ctx.beginPath();e=[this.options.windowSize,f.values.length,this.inTransition()];c=e[0];k=e[1];for(e=e[2];-2<=--c&&0<=--k;)b=f.values[k],b=[(c+1)*g+a,m(b.y+b.y0)],e&&(b[0]+=g),c===this.options.windowSize- -1?this.ctx.moveTo.apply(this.ctx,b):this.ctx.lineTo.apply(this.ctx,b);c=e?(c+3)*g+a:(c+2)*g+a;this.ctx.lineTo(c,this.innerHeight());this.ctx.lineTo(this.width*this.pixelRatio+g+a,this.innerHeight());this.ctx.closePath();p.push(this.ctx.fill())}return p};a.prototype._drawStrokes=function(a){var b,c,k,f,e,g,m,l,n,p;null==a&&(a=0);c=[this.y(),this.w()];m=c[0];g=c[1];p=[];for(c=l=n=this.data.length-1;0>=n?0>=l:0<=l;c=0>=n?++l:--l){f=this.data[c];this.setStyles(f);this.ctx.beginPath();e=[this.options.windowSize, -f.values.length,this.inTransition()];c=e[0];k=e[1];for(e=e[2];-2<=--c&&0<=--k;)b=f.values[k],b=[(c+1)*g+a,m(b.y+b.y0)],e&&(b[0]+=g),c===this.options.windowSize-1?this.ctx.moveTo.apply(this.ctx,b):this.ctx.lineTo.apply(this.ctx,b);p.push(this.ctx.stroke())}return p};a.prototype.draw=function(c){null==c&&(c=0);this.clear();this._drawAreas(c);this._drawStrokes(c);return a.__super__.draw.call(this)};return a}(Epoch.Time.Stack)}).call(this); -(function(){var e={}.hasOwnProperty,g=function(c,a){function d(){this.constructor=c}for(var b in a)e.call(a,b)&&(c[b]=a[b]);d.prototype=a.prototype;c.prototype=new d;c.__super__=a.prototype;return c};Epoch.Time.Bar=function(c){function a(){return a.__super__.constructor.apply(this,arguments)}g(a,c);a.prototype._offsetX=function(){return 0.5*this.w()/this.pixelRatio};a.prototype.setStyles=function(a){a=this.getStyles("rect.bar."+a.replace(/\s/g,"."));this.ctx.fillStyle=a.fill;this.ctx.strokeStyle= -null==a.stroke||"none"===a.stroke?"transparent":a.stroke;if(null!=a["stroke-width"])return this.ctx.lineWidth=a["stroke-width"].replace("px","")};a.prototype.draw=function(c){var b,h,k,f,e,g,m,l,n,p,r,s,t;null==c&&(c=0);this.clear();f=[this.y(),this.w()];p=f[0];n=f[1];t=this.data;r=0;for(s=t.length;r=e&&0<=--g;)b=m.values[g],k=[f*n+c, -b.y,b.y0],b=k[0],h=k[1],k=k[2],l&&(b+=n),b=[b+1,p(h+k),n-2,this.innerHeight()-p(h)+0.5*this.pixelRatio],this.ctx.fillRect.apply(this.ctx,b),this.ctx.strokeRect.apply(this.ctx,b);return a.__super__.draw.call(this)};return a}(Epoch.Time.Stack)}).call(this); -(function(){var e={}.hasOwnProperty,g=function(c,a){function d(){this.constructor=c}for(var b in a)e.call(a,b)&&(c[b]=a[b]);d.prototype=a.prototype;c.prototype=new d;c.__super__=a.prototype;return c};Epoch.Time.Gauge=function(c){function a(c){this.options=null!=c?c:{};a.__super__.constructor.call(this,this.options=Epoch.Util.defaults(this.options,d));this.value=this.options.value||0;"absolute"!==this.el.style("position")&&"relative"!==this.el.style("position")&&this.el.style("position","relative"); -this.svg=this.el.insert("svg",":first-child").attr("width",this.width).attr("height",this.height).attr("class","gauge-labels");this.svg.style({position:"absolute","z-index":"1"});this.svg.append("g").attr("transform","translate("+this.textX()+", "+this.textY()+")").append("text").attr("class","value").text(this.options.format(this.value));this.animation={interval:null,active:!1,delta:0,target:0};this._animate=function(a){return function(){Math.abs(a.animation.target-a.value)=t;b=0<=t?++s:--s)b=l(b),b=[Math.cos(b),Math.sin(b)],c=b[0],m=b[1],b=c*(g-n)+d,r=m*(g-n)+e,c=c*(g-n-p)+d,m=m*(g-n-p)+e,this.ctx.moveTo(b,r),this.ctx.lineTo(c,m);this.ctx.stroke();this.setStyles(".epoch .gauge .arc.outer");this.ctx.beginPath();this.ctx.arc(d,e,g,-1.125* -Math.PI,0.125*Math.PI,!1);this.ctx.stroke();this.setStyles(".epoch .gauge .arc.inner");this.ctx.beginPath();this.ctx.arc(d,e,g-10,-1.125*Math.PI,0.125*Math.PI,!1);this.ctx.stroke();this.drawNeedle();return a.__super__.draw.call(this)};a.prototype.drawNeedle=function(){var a,b,c;c=[this.centerX(),this.centerY(),this.radius()];a=c[0];b=c[1];c=c[2];this.setStyles(".epoch .gauge .needle");this.ctx.beginPath();this.ctx.save();this.ctx.translate(a,b);this.ctx.rotate(this.getAngle(this.value));this.ctx.moveTo(4* -this.pixelRatio,0);this.ctx.lineTo(-4*this.pixelRatio,0);this.ctx.lineTo(-1*this.pixelRatio,19-c);this.ctx.lineTo(1,19-c);this.ctx.fill();this.setStyles(".epoch .gauge .needle-base");this.ctx.beginPath();this.ctx.arc(0,0,this.getWidth()/25,0,2*Math.PI);this.ctx.fill();return this.ctx.restore()};a.prototype.domainChanged=function(){return this.draw()};a.prototype.ticksChanged=function(){return this.draw()};a.prototype.tickSizeChanged=function(){return this.draw()};a.prototype.tickOffsetChanged=function(){return this.draw()}; -a.prototype.formatChanged=function(){return this.svg.select("text.value").text(this.options.format(this.value))};return a}(Epoch.Chart.Canvas)}).call(this); -(function(){var e={}.hasOwnProperty,g=function(c,a){function d(){this.constructor=c}for(var b in a)e.call(a,b)&&(c[b]=a[b]);d.prototype=a.prototype;c.prototype=new d;c.__super__=a.prototype;return c};Epoch.Time.Heatmap=function(c){function a(c){this.options=c;a.__super__.constructor.call(this,this.options=Epoch.Util.defaults(this.options,b));this._setOpacityFunction();this._setupPaintCanvas();this.onAll(e)}var d,b,e;g(a,c);b={buckets:10,bucketRange:[0,100],opacity:"linear",bucketPadding:2,paintZeroValues:!1, -cutOutliers:!1};d={root:function(a,b){return Math.pow(a/b,0.5)},linear:function(a,b){return a/b},quadratic:function(a,b){return Math.pow(a/b,2)},cubic:function(a,b){return Math.pow(a/b,3)},quartic:function(a,b){return Math.pow(a/b,4)},quintic:function(a,b){return Math.pow(a/b,5)}};e={"option:buckets":"bucketsChanged","option:bucketRange":"bucketRangeChanged","option:opacity":"opacityChanged","option:bucketPadding":"bucketPaddingChanged","option:paintZeroValues":"paintZeroValuesChanged","option:cutOutliers":"cutOutliersChanged"}; -a.prototype._setOpacityFunction=function(){if(Epoch.isString(this.options.opacity)){if(this._opacityFn=d[this.options.opacity],null==this._opacityFn)return Epoch.exception("Unknown coloring function provided '"+this.options.opacity+"'")}else return Epoch.isFunction(this.options.opacity)?this._opacityFn=this.options.opacity:Epoch.exception("Unknown type for provided coloring function.")};a.prototype.setData=function(b){var c,d,e,g;a.__super__.setData.call(this,b);e=this.data;g=[];c=0;for(d=e.length;c< -d;c++)b=e[c],g.push(b.values=b.values.map(function(a){return function(b){return a._prepareEntry(b)}}(this)));return g};a.prototype._getBuckets=function(a){var b,c,d,e,g;e=a.time;g=[];b=0;for(d=this.options.buckets;0<=d?bd;0<=d?++b:--b)g.push(0);e={time:e,max:0,buckets:g};b=(this.options.bucketRange[1]-this.options.bucketRange[0])/this.options.buckets;g=a.histogram;for(c in g)a=g[c],d=parseInt((c-this.options.bucketRange[0])/b),this.options.cutOutliers&&(0>d||d>=this.options.buckets)||(0>d?d= -0:d>=this.options.buckets&&(d=this.options.buckets-1),e.buckets[d]+=parseInt(a));c=a=0;for(b=e.buckets.length;0<=b?ab;c=0<=b?++a:--a)e.max=Math.max(e.max,e.buckets[c]);return e};a.prototype.y=function(){return d3.scale.linear().domain(this.options.bucketRange).range([this.innerHeight(),0])};a.prototype.ySvg=function(){return d3.scale.linear().domain(this.options.bucketRange).range([this.innerHeight()/this.pixelRatio,0])};a.prototype.h=function(){return this.innerHeight()/this.options.buckets}; -a.prototype._offsetX=function(){return 0.5*this.w()/this.pixelRatio};a.prototype._setupPaintCanvas=function(){this.paintWidth=(this.options.windowSize+1)*this.w();this.paintHeight=this.height*this.pixelRatio;this.paint=document.createElement("CANVAS");this.paint.width=this.paintWidth;this.paint.height=this.paintHeight;this.p=Epoch.Util.getContext(this.paint);this.redraw();this.on("after:shift","_paintEntry");this.on("transition:end","_shiftPaintCanvas");return this.on("transition:end",function(a){return function(){return a.draw(a.animation.frame* -a.animation.delta())}}(this))};a.prototype.redraw=function(){var a,b;b=this.data[0].values.length;a=this.options.windowSize;for(this.inTransition()&&a++;0<=--b&&0<=--a;)this._paintEntry(b,a);return this.draw(this.animation.frame*this.animation.delta())};a.prototype._computeColor=function(a,b,c){return Epoch.Util.toRGBA(c,this._opacityFn(a,b))};a.prototype._paintEntry=function(a,b){var c,d,e,g,h,p,r,s,t,v,y,w,A,z;null==a&&(a=null);null==b&&(b=null);g=[this.w(),this.h()];y=g[0];p=g[1];null==a&&(a=this.data[0].values.length- -1);null==b&&(b=this.options.windowSize);g=[];var x;x=[];h=0;for(v=this.options.buckets;0<=v?hv;0<=v?++h:--h)x.push(0);v=0;t=this.data;d=0;for(r=t.length;d code[class*="language-"], -pre[class*="language-"] { - background: #f5f2f0; -} - -/* Inline code */ -:not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: slategray; -} - -.token.punctuation { - color: #999; -} - -.namespace { - opacity: .7; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol, -.token.deleted { - color: #905; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.inserted { - color: #690; -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string { - color: #a67f59; - background: hsla(0, 0%, 100%, .5); -} - -.token.atrule, -.token.attr-value, -.token.keyword { - color: #07a; -} - -.token.function { - color: #DD4A68; -} - -.token.regex, -.token.important, -.token.variable { - color: #e90; -} - -.token.important, -.token.bold { - font-weight: bold; -} -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - diff --git a/examples/realtime-advanced/resources/static/prismjs.min.js b/examples/realtime-advanced/resources/static/prismjs.min.js deleted file mode 100644 index a6855a7..0000000 --- a/examples/realtime-advanced/resources/static/prismjs.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/* http://prismjs.com/download.html?themes=prism&languages=clike+javascript+go */ -self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{};var Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var s="";for(var o in i.attributes)s+=o+'="'+(i.attributes[o]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+s+">"+i.content+""},!self.document)return self.addEventListener?(self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code;self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),self.close()},!1),self.Prism):self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);; -Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/("|')(\\\n|\\?.)*?\1/,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":{pattern:/[a-z0-9_]+\(/i,inside:{punctuation:/\(/}},number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|&{1,2}|\|?\||\?|\*|\/|~|\^|%/,ignore:/&(lt|gt|amp);/i,punctuation:/[{}[\];(),.:]/};; -Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|get|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|-?Infinity)\b/,"function":/(?!\d)[a-z0-9_$]+(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/[\w\W]*?<\/script>/i,inside:{tag:{pattern:/|<\/script>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript},alias:"language-javascript"}});; -Prism.languages.go=Prism.languages.extend("clike",{keyword:/\b(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,builtin:/\b(bool|byte|complex(64|128)|error|float(32|64)|rune|string|u?int(8|16|32|64|)|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(ln)?|real|recover)\b/,"boolean":/\b(_|iota|nil|true|false)\b/,operator:/([(){}\[\]]|[*\/%^!]=?|\+[=+]?|-[>=-]?|\|[=|]?|>[=>]?|<(<|[=-])?|==?|&(&|=|^=?)?|\.(\.\.)?|[,;]|:=?)/,number:/\b(-?(0x[a-f\d]+|(\d+\.?\d*|\.\d+)(e[-+]?\d+)?)i?)\b/i,string:/("|'|`)(\\?.|\r|\n)*?\1/}),delete Prism.languages.go["class-name"];; diff --git a/examples/realtime-advanced/resources/static/realtime.js b/examples/realtime-advanced/resources/static/realtime.js deleted file mode 100644 index 919dae2..0000000 --- a/examples/realtime-advanced/resources/static/realtime.js +++ /dev/null @@ -1,144 +0,0 @@ - - -function StartRealtime(roomid, timestamp) { - StartEpoch(timestamp); - StartSSE(roomid); - StartForm(); -} - -function StartForm() { - $('#chat-message').focus(); - $('#chat-form').ajaxForm(function() { - $('#chat-message').val(''); - $('#chat-message').focus(); - }); -} - -function StartEpoch(timestamp) { - var windowSize = 60; - var height = 200; - var defaultData = histogram(windowSize, timestamp); - - window.heapChart = $('#heapChart').epoch({ - type: 'time.area', - axes: ['bottom', 'left'], - height: height, - historySize: 10, - data: [ - {values: defaultData}, - {values: defaultData} - ] - }); - - window.mallocsChart = $('#mallocsChart').epoch({ - type: 'time.area', - axes: ['bottom', 'left'], - height: height, - historySize: 10, - data: [ - {values: defaultData}, - {values: defaultData} - ] - }); - - window.messagesChart = $('#messagesChart').epoch({ - type: 'time.line', - axes: ['bottom', 'left'], - height: 240, - historySize: 10, - data: [ - {values: defaultData}, - {values: defaultData}, - {values: defaultData} - ] - }); -} - -function StartSSE(roomid) { - if (!window.EventSource) { - alert("EventSource is not enabled in this browser"); - return; - } - var source = new EventSource('/stream/'+roomid); - source.addEventListener('message', newChatMessage, false); - source.addEventListener('stats', stats, false); -} - -function stats(e) { - var data = parseJSONStats(e.data); - heapChart.push(data.heap); - mallocsChart.push(data.mallocs); - messagesChart.push(data.messages); -} - -function parseJSONStats(e) { - var data = jQuery.parseJSON(e); - var timestamp = data.timestamp; - - var heap = [ - {time: timestamp, y: data.HeapInuse}, - {time: timestamp, y: data.StackInuse} - ]; - - var mallocs = [ - {time: timestamp, y: data.Mallocs}, - {time: timestamp, y: data.Frees} - ]; - var messages = [ - {time: timestamp, y: data.Connected}, - {time: timestamp, y: data.Inbound}, - {time: timestamp, y: data.Outbound} - ]; - - return { - heap: heap, - mallocs: mallocs, - messages: messages - } -} - -function newChatMessage(e) { - var data = jQuery.parseJSON(e.data); - var nick = data.nick; - var message = data.message; - var style = rowStyle(nick); - var html = ""+nick+""+message+""; - $('#chat').append(html); - - $("#chat-scroll").scrollTop($("#chat-scroll")[0].scrollHeight); -} - -function histogram(windowSize, timestamp) { - var entries = new Array(windowSize); - for(var i = 0; i < windowSize; i++) { - entries[i] = {time: (timestamp-windowSize+i-1), y:0}; - } - return entries; -} - -var entityMap = { - "&": "&", - "<": "<", - ">": ">", - '"': '"', - "'": ''', - "/": '/' -}; - -function rowStyle(nick) { - var classes = ['active', 'success', 'info', 'warning', 'danger']; - var index = hashCode(nick)%5; - return classes[index]; -} - -function hashCode(s){ - return Math.abs(s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0)); -} - -function escapeHtml(string) { - return String(string).replace(/[&<>"'\/]/g, function (s) { - return entityMap[s]; - }); -} - -window.StartRealtime = StartRealtime diff --git a/examples/realtime-advanced/rooms.go b/examples/realtime-advanced/rooms.go deleted file mode 100644 index 82396ba..0000000 --- a/examples/realtime-advanced/rooms.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import "github.com/dustin/go-broadcast" - -var roomChannels = make(map[string]broadcast.Broadcaster) - -func openListener(roomid string) chan interface{} { - listener := make(chan interface{}) - room(roomid).Register(listener) - return listener -} - -func closeListener(roomid string, listener chan interface{}) { - room(roomid).Unregister(listener) - close(listener) -} - -func room(roomid string) broadcast.Broadcaster { - b, ok := roomChannels[roomid] - if !ok { - b = broadcast.NewBroadcaster(10) - roomChannels[roomid] = b - } - return b -} diff --git a/examples/realtime-advanced/routes.go b/examples/realtime-advanced/routes.go deleted file mode 100644 index 03c6991..0000000 --- a/examples/realtime-advanced/routes.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "fmt" - "html" - "io" - "net/http" - "strings" - "time" - - "github.com/gin-gonic/gin" -) - -func rateLimit(c *gin.Context) { - ip := c.ClientIP() - value := int(ips.Add(ip, 1)) - if value%50 == 0 { - fmt.Printf("ip: %s, count: %d\n", ip, value) - } - if value >= 200 { - if value%200 == 0 { - fmt.Println("ip blocked") - } - c.Abort() - c.String(http.StatusServiceUnavailable, "you were automatically banned :)") - } -} - -func index(c *gin.Context) { - c.Redirect(http.StatusMovedPermanently, "/room/hn") -} - -func roomGET(c *gin.Context) { - roomid := c.Param("roomid") - nick := c.Query("nick") - if len(nick) < 2 { - nick = "" - } - if len(nick) > 13 { - nick = nick[0:12] + "..." - } - c.HTML(http.StatusOK, "room_login.templ.html", gin.H{ - "roomid": roomid, - "nick": nick, - "timestamp": time.Now().Unix(), - }) - -} - -func roomPOST(c *gin.Context) { - roomid := c.Param("roomid") - nick := c.Query("nick") - message := c.PostForm("message") - message = strings.TrimSpace(message) - - validMessage := len(message) > 1 && len(message) < 200 - validNick := len(nick) > 1 && len(nick) < 14 - if !validMessage || !validNick { - c.JSON(http.StatusBadRequest, gin.H{ - "status": "failed", - "error": "the message or nickname is too long", - }) - return - } - - post := gin.H{ - "nick": html.EscapeString(nick), - "message": html.EscapeString(message), - } - messages.Add("inbound", 1) - room(roomid).Submit(post) - c.JSON(http.StatusOK, post) -} - -func streamRoom(c *gin.Context) { - roomid := c.Param("roomid") - listener := openListener(roomid) - ticker := time.NewTicker(1 * time.Second) - users.Add("connected", 1) - defer func() { - closeListener(roomid, listener) - ticker.Stop() - users.Add("disconnected", 1) - }() - - c.Stream(func(w io.Writer) bool { - select { - case msg := <-listener: - messages.Add("outbound", 1) - c.SSEvent("message", msg) - case <-ticker.C: - c.SSEvent("stats", Stats()) - } - return true - }) -} diff --git a/examples/realtime-advanced/stats.go b/examples/realtime-advanced/stats.go deleted file mode 100644 index a648803..0000000 --- a/examples/realtime-advanced/stats.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "runtime" - "sync" - "time" - - "github.com/manucorporat/stats" -) - -var ( - ips = stats.New() - messages = stats.New() - users = stats.New() - mutexStats sync.RWMutex - savedStats map[string]uint64 -) - -func statsWorker() { - c := time.Tick(1 * time.Second) - var lastMallocs uint64 - var lastFrees uint64 - for range c { - var stats runtime.MemStats - runtime.ReadMemStats(&stats) - - mutexStats.Lock() - savedStats = map[string]uint64{ - "timestamp": uint64(time.Now().Unix()), - "HeapInuse": stats.HeapInuse, - "StackInuse": stats.StackInuse, - "Mallocs": stats.Mallocs - lastMallocs, - "Frees": stats.Frees - lastFrees, - "Inbound": uint64(messages.Get("inbound")), - "Outbound": uint64(messages.Get("outbound")), - "Connected": connectedUsers(), - } - lastMallocs = stats.Mallocs - lastFrees = stats.Frees - messages.Reset() - mutexStats.Unlock() - } -} - -func connectedUsers() uint64 { - connected := users.Get("connected") - users.Get("disconnected") - if connected < 0 { - return 0 - } - return uint64(connected) -} - -// Stats returns savedStats data. -func Stats() map[string]uint64 { - mutexStats.RLock() - defer mutexStats.RUnlock() - - return savedStats -} diff --git a/examples/realtime-chat/Makefile b/examples/realtime-chat/Makefile deleted file mode 100644 index dea583d..0000000 --- a/examples/realtime-chat/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -all: deps build - -.PHONY: deps -deps: - go get -d -v github.com/dustin/go-broadcast/... - -.PHONY: build -build: deps - go build -o realtime-chat main.go rooms.go template.go diff --git a/examples/realtime-chat/main.go b/examples/realtime-chat/main.go deleted file mode 100644 index 5741fcb..0000000 --- a/examples/realtime-chat/main.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "fmt" - "io" - "math/rand" - "net/http" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - router.SetHTMLTemplate(html) - - router.GET("/room/:roomid", roomGET) - router.POST("/room/:roomid", roomPOST) - router.DELETE("/room/:roomid", roomDELETE) - router.GET("/stream/:roomid", stream) - - router.Run(":8080") -} - -func stream(c *gin.Context) { - roomid := c.Param("roomid") - listener := openListener(roomid) - defer closeListener(roomid, listener) - - c.Stream(func(w io.Writer) bool { - c.SSEvent("message", <-listener) - return true - }) -} - -func roomGET(c *gin.Context) { - roomid := c.Param("roomid") - userid := fmt.Sprint(rand.Int31()) - c.HTML(http.StatusOK, "chat_room", gin.H{ - "roomid": roomid, - "userid": userid, - }) -} - -func roomPOST(c *gin.Context) { - roomid := c.Param("roomid") - userid := c.PostForm("user") - message := c.PostForm("message") - room(roomid).Submit(userid + ": " + message) - - c.JSON(http.StatusOK, gin.H{ - "status": "success", - "message": message, - }) -} - -func roomDELETE(c *gin.Context) { - roomid := c.Param("roomid") - deleteBroadcast(roomid) -} diff --git a/examples/realtime-chat/rooms.go b/examples/realtime-chat/rooms.go deleted file mode 100644 index 8c62bec..0000000 --- a/examples/realtime-chat/rooms.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import "github.com/dustin/go-broadcast" - -var roomChannels = make(map[string]broadcast.Broadcaster) - -func openListener(roomid string) chan interface{} { - listener := make(chan interface{}) - room(roomid).Register(listener) - return listener -} - -func closeListener(roomid string, listener chan interface{}) { - room(roomid).Unregister(listener) - close(listener) -} - -func deleteBroadcast(roomid string) { - b, ok := roomChannels[roomid] - if ok { - b.Close() - delete(roomChannels, roomid) - } -} - -func room(roomid string) broadcast.Broadcaster { - b, ok := roomChannels[roomid] - if !ok { - b = broadcast.NewBroadcaster(10) - roomChannels[roomid] = b - } - return b -} diff --git a/examples/realtime-chat/template.go b/examples/realtime-chat/template.go deleted file mode 100644 index cc1ab9b..0000000 --- a/examples/realtime-chat/template.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import "html/template" - -var html = template.Must(template.New("chat_room").Parse(` - - - {{.roomid}} - - - - - - -

Welcome to {{.roomid}} room

-
-
- User: - Message: - -
- - -`)) diff --git a/examples/struct-lvl-validations/README.md b/examples/struct-lvl-validations/README.md deleted file mode 100644 index 1bd57f0..0000000 --- a/examples/struct-lvl-validations/README.md +++ /dev/null @@ -1,50 +0,0 @@ -## 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 deleted file mode 100644 index be807b7..0000000 --- a/examples/struct-lvl-validations/server.go +++ /dev/null @@ -1,64 +0,0 @@ -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 deleted file mode 100644 index e20a3b9..0000000 --- a/examples/template/main.go +++ /dev/null @@ -1,32 +0,0 @@ -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("../../testdata/template/raw.tmpl") - - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - - router.Run(":8080") -} diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go deleted file mode 100644 index 2b9d6d9..0000000 --- a/examples/upload-file/multiple/main.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "path/filepath" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.Static("/", "./public") - router.POST("/upload", func(c *gin.Context) { - name := c.PostForm("name") - email := c.PostForm("email") - - // Multipart form - form, err := c.MultipartForm() - if err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) - return - } - files := form.File["files"] - - for _, file := range files { - filename := filepath.Base(file.Filename) - if err := c.SaveUploadedFile(file, filename); err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) - return - } - } - - c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email)) - }) - router.Run(":8080") -} diff --git a/examples/upload-file/multiple/public/index.html b/examples/upload-file/multiple/public/index.html deleted file mode 100644 index b846360..0000000 --- a/examples/upload-file/multiple/public/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Multiple file upload - - -

Upload multiple files with fields

- -
- Name:
- Email:
- Files:

- -
- - diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go deleted file mode 100644 index ba289f5..0000000 --- a/examples/upload-file/single/main.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "path/filepath" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.Static("/", "./public") - router.POST("/upload", func(c *gin.Context) { - name := c.PostForm("name") - email := c.PostForm("email") - - // Source - file, err := c.FormFile("file") - if err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) - return - } - - filename := filepath.Base(file.Filename) - if err := c.SaveUploadedFile(file, filename); err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) - return - } - - c.String(http.StatusOK, fmt.Sprintf("File %s uploaded successfully with fields name=%s and email=%s.", file.Filename, name, email)) - }) - router.Run(":8080") -} diff --git a/examples/upload-file/single/public/index.html b/examples/upload-file/single/public/index.html deleted file mode 100644 index b0c2a80..0000000 --- a/examples/upload-file/single/public/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Single file upload - - -

Upload single file with fields

- -
- Name:
- Email:
- Files:

- -
- diff --git a/go.mod b/go.mod index 6f9d68d..5963e01 100644 --- a/go.mod +++ b/go.mod @@ -1,32 +1,18 @@ module github.com/gin-gonic/gin require ( - github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482 + github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 github.com/golang/protobuf v1.2.0 github.com/json-iterator/go v1.1.5 github.com/mattn/go-isatty v0.0.4 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/stretchr/testify v1.3.0 - github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 - golang.org/x/net v0.0.0-20190119204137-ed066c81e75e - golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 - golang.org/x/sys v0.0.0-20190124100055-b90733256f2e // indirect + github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 + golang.org/x/net v0.0.0-20190213061140-3a22650c66bd + golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect + golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/yaml.v2 v2.2.2 ) - -exclude ( - github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160 - github.com/client9/misspell v0.3.4 - github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 - github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768 - github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 - github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 - github.com/newrelic/go-agent v2.5.0+incompatible - github.com/thinkerou/favicon v0.1.0 - golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b - golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 - google.golang.org/grpc v1.18.0 -) diff --git a/go.sum b/go.sum index 95e2b4f..d864be2 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,10 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160 h1:HJpuhXOHC4EkXDARsLjmXAV9FhlY6qFDnKI/MJM6eoE= -github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482 h1:iOz5sIQUvuOlpiC7Q6+MmJQpWnlneYX98QIGf+2m50Y= -github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 h1:FaI7wNyesdMBSkIRVUuEEYEvmzufs7EqQvRAxfEXGbQ= +github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -22,28 +13,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY+LsWmuwob+CRS1BmdRdjphAm9mH4= -github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q= -golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs= +github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA= +github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e h1:3GIlrlVLfkoipSReOMNAgApI0ajnalyLa/EZHHca/XI= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= -google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0 h1:bzeyCHgoAyjZjAhvTpks+qM7sdlh4cCSitmXeCEO3B4= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= @@ -51,4 +30,3 @@ gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2G gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vendor/vendor.json b/vendor/vendor.json index af1a014..6050e8f 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -40,6 +40,12 @@ "version": "v0.0", "versionExact": "v0.0.4" }, + { + "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", + "path": "github.com/pmezard/go-difflib/difflib", + "revision": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc", + "revisionTime": "2018-12-26T10:54:42Z" + }, { "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", From 688eb1281c6c027fbf44d9af9aea9abe32794d07 Mon Sep 17 00:00:00 2001 From: Dang Nguyen Date: Sat, 2 Mar 2019 15:04:21 +0700 Subject: [PATCH 318/912] update examples link in README (#1789) --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a4ced64..6cb0e78 100644 --- a/README.md +++ b/README.md @@ -359,7 +359,7 @@ ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] #### Single file -References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single). +References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single). `file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) @@ -394,7 +394,7 @@ curl -X POST http://localhost:8080/upload \ #### Multiple files -See the detail [example code](examples/upload-file/multiple). +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). ```go func main() { @@ -726,7 +726,7 @@ When running the above example using the above the `curl` command, it returns er ### Custom Validators -It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go). +It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). ```go package main @@ -790,7 +790,7 @@ $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" ``` [Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. -See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more. +See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. ### Only Bind Query String @@ -1280,7 +1280,7 @@ You may use custom delims #### Custom Template Funcs -See the detail [example code](examples/template). +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). main.go @@ -1654,7 +1654,7 @@ An alternative to endless: * [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. * [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. -If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](./examples/graceful-shutdown) example with gin. +If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown) example with gin. ```go // +build go1.8 @@ -1758,7 +1758,7 @@ func loadTemplate() (*template.Template, error) { } ``` -See a complete example in the `examples/assets-in-binary` directory. +See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary` directory. ### Bind form-data request with custom struct From 8c8002d7449979ab65d22794be159751dbc1c20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 2 Mar 2019 19:21:10 +0800 Subject: [PATCH 319/912] chore: add examples repo link to README (#1788) --- README.md | 2 ++ examples/README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cb0e78..20e3e58 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,8 @@ $ go build -tags=jsoniter . ## API Examples +You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). + ### Using GET, POST, PUT, PATCH, DELETE and OPTIONS ```go diff --git a/examples/README.md b/examples/README.md index 4b3b718..b02deae 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,3 @@ # Gin Examples -## TODO +⚠️ **NOTICE:** All gin examples has moved as alone repository to [here](https://github.com/gin-gonic/examples). From 3b84a430d0a6307b1f788952520db268bc3effe4 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 2 Mar 2019 20:19:42 +0800 Subject: [PATCH 320/912] Drone switch from gin to go-chi in 1.0 version. (#1790) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 20e3e58..a22440f 100644 --- a/README.md +++ b/README.md @@ -2082,7 +2082,6 @@ func TestPingRoute(t *testing.T) { Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. -* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go. * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. From 893c6cae07ef564cbdd2796589c449dd2ac87d21 Mon Sep 17 00:00:00 2001 From: Daniel Krom Date: Sat, 2 Mar 2019 17:07:37 +0200 Subject: [PATCH 321/912] Added stream flag indicates if client disconnected in middle of streaming (#1252) --- context.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index e9735d2..5dc7f8a 100644 --- a/context.go +++ b/context.go @@ -896,19 +896,20 @@ func (c *Context) SSEvent(name string, message interface{}) { }) } -// Stream sends a streaming response. -func (c *Context) Stream(step func(w io.Writer) bool) { +// Stream sends a streaming response and returns a boolean +// indicates "Is client disconnected in middle of stream" +func (c *Context) Stream(step func(w io.Writer) bool) bool { w := c.Writer clientGone := w.CloseNotify() for { select { case <-clientGone: - return + return true default: keepOpen := step(w) w.Flush() if !keepOpen { - return + return false } } } From 0d50ce859745354fa83dcf2bf4c972abed25e53b Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Sun, 3 Mar 2019 09:39:43 +0300 Subject: [PATCH 322/912] refactor(form_mapping.go): mapping ptr, struct and map (#1749) * refactor(form_mapping.go): mapping ptr, struct and map * fix #1672 correct work with ptr - not create value if field is not set * avoid allocations on strings.Split() - change to strings.Index() * fix #610 tag value "-" is mean ignoring field * struct fields mapped like json.Unmarshal * map fields mapped like json.Unmarshal * fix after @thinkerou review --- README.md | 18 -- binding/binding_test.go | 134 ++++++++++++--- binding/form_mapping.go | 224 +++++++++++++++---------- binding/form_mapping_benchmark_test.go | 61 +++++++ 4 files changed, 314 insertions(+), 123 deletions(-) create mode 100644 binding/form_mapping_benchmark_test.go diff --git a/README.md b/README.md index a22440f..eb9415f 100644 --- a/README.md +++ b/README.md @@ -1836,24 +1836,6 @@ $ 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 have form -} - -type StructZ struct { - Z *StructZ `form:"name_z"` // HERE have 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 diff --git a/binding/binding_test.go b/binding/binding_test.go index c9dea34..16ca202 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -12,6 +12,7 @@ import ( "mime/multipart" "net/http" "strconv" + "strings" "testing" "time" @@ -57,7 +58,6 @@ type FooStructForTimeTypeFailLocation struct { } type FooStructForMapType struct { - // Unknown type: not support map MapFoo map[string]interface{} `form:"map_foo"` } @@ -303,7 +303,7 @@ func TestBindingFormInvalidName2(t *testing.T) { func TestBindingFormForType(t *testing.T) { testFormBindingForType(t, "POST", "/", "/", - "map_foo=", "bar2=1", "Map") + "map_foo={\"bar\":123}", "map_foo=1", "Map") testFormBindingForType(t, "POST", "/", "/", @@ -508,20 +508,30 @@ func TestBindingYAMLFail(t *testing.T) { `foo:\nbar`, `bar: foo`) } -func createFormPostRequest() *http.Request { - req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) +func createFormPostRequest(t *testing.T) *http.Request { + req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) + assert.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } -func createDefaultFormPostRequest() *http.Request { - req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) +func createDefaultFormPostRequest(t *testing.T) *http.Request { + req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) + assert.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } -func createFormPostRequestFail() *http.Request { - req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar")) +func createFormPostRequestForMap(t *testing.T) *http.Request { + req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) + assert.NoError(t, err) + req.Header.Set("Content-Type", MIMEPOSTForm) + return req +} + +func createFormPostRequestForMapFail(t *testing.T) *http.Request { + req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) + assert.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } @@ -535,26 +545,42 @@ func createFormMultipartRequest(t *testing.T) *http.Request { assert.NoError(t, mw.SetBoundary(boundary)) assert.NoError(t, mw.WriteField("foo", "bar")) assert.NoError(t, mw.WriteField("bar", "foo")) - req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + assert.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } -func createFormMultipartRequestFail(t *testing.T) *http.Request { +func createFormMultipartRequestForMap(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("map_foo", "bar")) - req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) + assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) + req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) + assert.NoError(t, err) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + return req +} + +func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("map_foo", "3.14")) + req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) + assert.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } func TestBindingFormPost(t *testing.T) { - req := createFormPostRequest() + req := createFormPostRequest(t) var obj FooBarStruct assert.NoError(t, FormPost.Bind(req, &obj)) @@ -564,7 +590,7 @@ func TestBindingFormPost(t *testing.T) { } func TestBindingDefaultValueFormPost(t *testing.T) { - req := createDefaultFormPostRequest() + req := createDefaultFormPostRequest(t) var obj FooDefaultBarStruct assert.NoError(t, FormPost.Bind(req, &obj)) @@ -572,8 +598,16 @@ func TestBindingDefaultValueFormPost(t *testing.T) { assert.Equal(t, "hello", obj.Bar) } -func TestBindingFormPostFail(t *testing.T) { - req := createFormPostRequestFail() +func TestBindingFormPostForMap(t *testing.T) { + req := createFormPostRequestForMap(t) + var obj FooStructForMapType + err := FormPost.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) +} + +func TestBindingFormPostForMapFail(t *testing.T) { + req := createFormPostRequestForMapFail(t) var obj FooStructForMapType err := FormPost.Bind(req, &obj) assert.Error(t, err) @@ -589,8 +623,18 @@ func TestBindingFormMultipart(t *testing.T) { assert.Equal(t, "foo", obj.Bar) } -func TestBindingFormMultipartFail(t *testing.T) { - req := createFormMultipartRequestFail(t) +func TestBindingFormMultipartForMap(t *testing.T) { + req := createFormMultipartRequestForMap(t) + var obj FooStructForMapType + err := FormMultipart.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) + assert.Equal(t, "thinkerou", obj.MapFoo["name"].(string)) + assert.Equal(t, float64(3.14), obj.MapFoo["pai"].(float64)) +} + +func TestBindingFormMultipartForMapFail(t *testing.T) { + req := createFormMultipartRequestForMapFail(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) assert.Error(t, err) @@ -773,6 +817,17 @@ func TestFormBindingFail(t *testing.T) { assert.Error(t, err) } +func TestFormBindingMultipartFail(t *testing.T) { + obj := FooBarStruct{} + req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar")) + assert.NoError(t, err) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary") + _, err = req.MultipartReader() + assert.NoError(t, err) + err = Form.Bind(req, &obj) + assert.Error(t, err) +} + func TestFormPostBindingFail(t *testing.T) { b := FormPost assert.Equal(t, "form-urlencoded", b.Name()) @@ -1109,7 +1164,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s case "Map": obj := FooStructForMapType{} err := b.Bind(req, &obj) - assert.Error(t, err) + assert.NoError(t, err) + assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) case "SliceMap": obj := FooStructForSliceMapType{} err := b.Bind(req, &obj) @@ -1317,3 +1373,43 @@ func TestCanSet(t *testing.T) { var c CanSetStruct assert.Nil(t, mapForm(&c, nil)) } + +func formPostRequest(path, body string) *http.Request { + req := requestWithBody("POST", path, body) + req.Header.Add("Content-Type", MIMEPOSTForm) + return req +} + +func TestBindingSliceDefault(t *testing.T) { + var s struct { + Friends []string `form:"friends,default=mike"` + } + req := formPostRequest("", "") + err := Form.Bind(req, &s) + assert.NoError(t, err) + + assert.Len(t, s.Friends, 1) + assert.Equal(t, "mike", s.Friends[0]) +} + +func TestBindingStructField(t *testing.T) { + var s struct { + Opts struct { + Port int + } `form:"opts"` + } + req := formPostRequest("", `opts={"Port": 8000}`) + err := Form.Bind(req, &s) + assert.NoError(t, err) + assert.Equal(t, 8000, s.Opts.Port) +} + +func TestBindingUnknownTypeChan(t *testing.T) { + var s struct { + Stop chan bool `form:"stop"` + } + req := formPostRequest("", "stop=true") + err := Form.Bind(req, &s) + assert.Error(t, err) + assert.Equal(t, errUnknownType, err) +} diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 8eb5c0d..1109e4d 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -5,6 +5,7 @@ package binding import ( + "encoding/json" "errors" "reflect" "strconv" @@ -12,6 +13,8 @@ import ( "time" ) +var errUnknownType = errors.New("Unknown type") + func mapUri(ptr interface{}, m map[string][]string) error { return mapFormByTag(ptr, m, "uri") } @@ -20,124 +23,153 @@ func mapForm(ptr interface{}, form map[string][]string) error { return mapFormByTag(ptr, form, "form") } +var emptyField = reflect.StructField{} + func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { - typ := reflect.TypeOf(ptr).Elem() - val := reflect.ValueOf(ptr).Elem() - for i := 0; i < typ.NumField(); i++ { - typeField := typ.Field(i) - structField := val.Field(i) - if !structField.CanSet() { - continue - } + _, err := mapping(reflect.ValueOf(ptr), emptyField, form, tag) + return err +} - structFieldKind := structField.Kind() - inputFieldName := typeField.Tag.Get(tag) - inputFieldNameList := strings.Split(inputFieldName, ",") - inputFieldName = inputFieldNameList[0] - var defaultValue string - if len(inputFieldNameList) > 1 { - defaultList := strings.SplitN(inputFieldNameList[1], "=", 2) - if defaultList[0] == "default" { - defaultValue = defaultList[1] - } +func mapping(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { + var vKind = value.Kind() + + if vKind == reflect.Ptr { + var isNew bool + vPtr := value + if value.IsNil() { + isNew = true + vPtr = reflect.New(value.Type().Elem()) } - if inputFieldName == "-" { - continue + isSetted, err := mapping(vPtr.Elem(), field, form, tag) + if err != nil { + return false, err } - if inputFieldName == "" { - inputFieldName = typeField.Name - - // if "form" tag is nil, we inspect if the field is a struct or struct pointer. - // this would not make sense for JSON parsing but it does for a form - // since data is flatten - if structFieldKind == reflect.Ptr { - if !structField.Elem().IsValid() { - structField.Set(reflect.New(structField.Type().Elem())) - } - structField = structField.Elem() - structFieldKind = structField.Kind() - } - if structFieldKind == reflect.Struct { - err := mapFormByTag(structField.Addr().Interface(), form, tag) - if err != nil { - return err - } - continue - } + if isNew && isSetted { + value.Set(vPtr) } - inputValue, exists := form[inputFieldName] + return isSetted, nil + } - if !exists { - if defaultValue == "" { + ok, err := tryToSetValue(value, field, form, tag) + if err != nil { + return false, err + } + if ok { + return true, nil + } + + if vKind == reflect.Struct { + tValue := value.Type() + + var isSetted bool + for i := 0; i < value.NumField(); i++ { + if !value.Field(i).CanSet() { continue } - inputValue = make([]string, 1) - inputValue[0] = defaultValue + ok, err := mapping(value.Field(i), tValue.Field(i), form, tag) + if err != nil { + return false, err + } + isSetted = isSetted || ok } + return isSetted, nil + } + return false, nil +} - numElems := len(inputValue) - if structFieldKind == reflect.Slice && numElems > 0 { - sliceOf := structField.Type().Elem().Kind() - slice := reflect.MakeSlice(structField.Type(), numElems, numElems) - for i := 0; i < numElems; i++ { - if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil { - return err - } - } - val.Field(i).Set(slice) - continue +func tryToSetValue(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { + var tagValue, defaultValue string + var isDefaultExists bool + + tagValue = field.Tag.Get(tag) + tagValue, opts := head(tagValue, ",") + + if tagValue == "-" { // just ignoring this field + return false, nil + } + if tagValue == "" { // default value is FieldName + tagValue = field.Name + } + if tagValue == "" { // when field is "emptyField" variable + return false, nil + } + + var opt string + for len(opts) > 0 { + opt, opts = head(opts, ",") + + k, v := head(opt, "=") + switch k { + case "default": + isDefaultExists = true + defaultValue = v } - if _, isTime := structField.Interface().(time.Time); isTime { - if err := setTimeField(inputValue[0], typeField, structField); err != nil { - return err - } - continue + } + + vs, ok := form[tagValue] + if !ok && !isDefaultExists { + return false, nil + } + + switch value.Kind() { + case reflect.Slice: + if !ok { + vs = []string{defaultValue} } - if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil { - return err + return true, setSlice(vs, value, field) + default: + var val string + if !ok { + val = defaultValue + } + + if len(vs) > 0 { + val = vs[0] } + return true, setWithProperType(val, value, field) } - return nil } -func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error { - switch valueKind { +func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { + switch value.Kind() { case reflect.Int: - return setIntField(val, 0, structField) + return setIntField(val, 0, value) case reflect.Int8: - return setIntField(val, 8, structField) + return setIntField(val, 8, value) case reflect.Int16: - return setIntField(val, 16, structField) + return setIntField(val, 16, value) case reflect.Int32: - return setIntField(val, 32, structField) + return setIntField(val, 32, value) case reflect.Int64: - return setIntField(val, 64, structField) + return setIntField(val, 64, value) case reflect.Uint: - return setUintField(val, 0, structField) + return setUintField(val, 0, value) case reflect.Uint8: - return setUintField(val, 8, structField) + return setUintField(val, 8, value) case reflect.Uint16: - return setUintField(val, 16, structField) + return setUintField(val, 16, value) case reflect.Uint32: - return setUintField(val, 32, structField) + return setUintField(val, 32, value) case reflect.Uint64: - return setUintField(val, 64, structField) + return setUintField(val, 64, value) case reflect.Bool: - return setBoolField(val, structField) + return setBoolField(val, value) case reflect.Float32: - return setFloatField(val, 32, structField) + return setFloatField(val, 32, value) case reflect.Float64: - return setFloatField(val, 64, structField) + return setFloatField(val, 64, value) case reflect.String: - structField.SetString(val) - case reflect.Ptr: - if !structField.Elem().IsValid() { - structField.Set(reflect.New(structField.Type().Elem())) + value.SetString(val) + case reflect.Struct: + switch value.Interface().(type) { + case time.Time: + return setTimeField(val, field, value) } - structFieldElem := structField.Elem() - return setWithProperType(structFieldElem.Kind(), val, structFieldElem) + return json.Unmarshal([]byte(val), value.Addr().Interface()) + case reflect.Map: + return json.Unmarshal([]byte(val), value.Addr().Interface()) default: - return errors.New("Unknown type") + return errUnknownType } return nil } @@ -218,3 +250,23 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val value.Set(reflect.ValueOf(t)) return nil } + +func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { + slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) + for i, s := range vals { + err := setWithProperType(s, slice.Index(i), field) + if err != nil { + return err + } + } + value.Set(slice) + return nil +} + +func head(str, sep string) (head string, tail string) { + idx := strings.Index(str, sep) + if idx < 0 { + return str, "" + } + return str[:idx], str[idx+len(sep):] +} diff --git a/binding/form_mapping_benchmark_test.go b/binding/form_mapping_benchmark_test.go new file mode 100644 index 0000000..0ef08f0 --- /dev/null +++ b/binding/form_mapping_benchmark_test.go @@ -0,0 +1,61 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +var form = map[string][]string{ + "name": {"mike"}, + "friends": {"anna", "nicole"}, + "id_number": {"12345678"}, + "id_date": {"2018-01-20"}, +} + +type structFull struct { + Name string `form:"name"` + Age int `form:"age,default=25"` + Friends []string `form:"friends"` + ID *struct { + Number string `form:"id_number"` + DateOfIssue time.Time `form:"id_date" time_format:"2006-01-02" time_utc:"true"` + } + Nationality *string `form:"nationality"` +} + +func BenchmarkMapFormFull(b *testing.B) { + var s structFull + for i := 0; i < b.N; i++ { + mapForm(&s, form) + } + b.StopTimer() + + t := b + assert.Equal(t, "mike", s.Name) + assert.Equal(t, 25, s.Age) + assert.Equal(t, []string{"anna", "nicole"}, s.Friends) + assert.Equal(t, "12345678", s.ID.Number) + assert.Equal(t, time.Date(2018, 1, 20, 0, 0, 0, 0, time.UTC), s.ID.DateOfIssue) + assert.Nil(t, s.Nationality) +} + +type structName struct { + Name string `form:"name"` +} + +func BenchmarkMapFormName(b *testing.B) { + var s structName + for i := 0; i < b.N; i++ { + mapForm(&s, form) + } + b.StopTimer() + + t := b + assert.Equal(t, "mike", s.Name) +} From df366c7840199276b3828c55d2a48588b5c15633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 4 Mar 2019 07:28:03 +0800 Subject: [PATCH 323/912] chore: update go mod package (#1792) --- go.mod | 12 ++++++------ go.sum | 31 +++++++++++++++++++------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 5963e01..0122757 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,17 @@ module github.com/gin-gonic/gin +go 1.12 + require ( - github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 - github.com/golang/protobuf v1.2.0 + github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 + github.com/golang/protobuf v1.3.0 github.com/json-iterator/go v1.1.5 - github.com/mattn/go-isatty v0.0.4 + github.com/mattn/go-isatty v0.0.6 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/stretchr/testify v1.3.0 github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 - golang.org/x/net v0.0.0-20190213061140-3a22650c66bd - golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect - golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0 // indirect + golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index d864be2..84cf837 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,18 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 h1:FaI7wNyesdMBSkIRVUuEEYEvmzufs7EqQvRAxfEXGbQ= -github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA= +github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= @@ -17,14 +21,17 @@ github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs= github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA= github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0 h1:bzeyCHgoAyjZjAhvTpks+qM7sdlh4cCSitmXeCEO3B4= -golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU= +golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= From f8f145961971af45de677fd81e7eae0b07b820c8 Mon Sep 17 00:00:00 2001 From: Kumar McMillan Date: Sun, 3 Mar 2019 18:06:46 -0600 Subject: [PATCH 324/912] Fix URL to starter template in the docs (#1795) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb9415f..28ebd74 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ $ govendor fetch github.com/gin-gonic/gin@v1.3 4. Copy a starting template inside your project ```sh -$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go +$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go ``` 5. Run your project From 805b2d490481d348856fb652473e73b25daf5aa2 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Mon, 4 Mar 2019 06:37:46 +0300 Subject: [PATCH 325/912] add support time.Duration on mapping (#1794) --- binding/binding_test.go | 17 +++++++++++++++++ binding/form_mapping.go | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/binding/binding_test.go b/binding/binding_test.go index 16ca202..5ae8795 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1413,3 +1413,20 @@ func TestBindingUnknownTypeChan(t *testing.T) { assert.Error(t, err) assert.Equal(t, errUnknownType, err) } + +func TestBindingTimeDuration(t *testing.T) { + var s struct { + Timeout time.Duration `form:"timeout"` + } + + // ok + req := formPostRequest("", "timeout=5s") + err := Form.Bind(req, &s) + assert.NoError(t, err) + assert.Equal(t, 5*time.Second, s.Timeout) + + // error + req = formPostRequest("", "timeout=wrong") + err = Form.Bind(req, &s) + assert.Error(t, err) +} diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 1109e4d..91fadcf 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -141,6 +141,10 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel case reflect.Int32: return setIntField(val, 32, value) case reflect.Int64: + switch value.Interface().(type) { + case time.Duration: + return setTimeDuration(val, value, field) + } return setIntField(val, 64, value) case reflect.Uint: return setUintField(val, 0, value) @@ -263,6 +267,15 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err return nil } +func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error { + d, err := time.ParseDuration(val) + if err != nil { + return err + } + value.Set(reflect.ValueOf(d)) + return nil +} + func head(str, sep string) (head string, tail string) { idx := strings.Index(str, sep) if idx < 0 { From a5dda62cdc30f28fd27f7a7fb2facbd06eb3520f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 5 Mar 2019 06:46:18 +0800 Subject: [PATCH 326/912] chore: use internal/json (#1791) --- binding/form_mapping.go | 3 ++- internal/json/json.go | 2 ++ internal/json/jsoniter.go | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 91fadcf..87edfbb 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -5,12 +5,13 @@ package binding import ( - "encoding/json" "errors" "reflect" "strconv" "strings" "time" + + "github.com/gin-gonic/gin/internal/json" ) var errUnknownType = errors.New("Unknown type") diff --git a/internal/json/json.go b/internal/json/json.go index 419d35f..480e8bf 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -11,6 +11,8 @@ import "encoding/json" var ( // Marshal is exported by gin/json package. Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent // NewDecoder is exported by gin/json package. diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index 2021c53..fabd7b8 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -12,6 +12,8 @@ var ( json = jsoniter.ConfigCompatibleWithStandardLibrary // Marshal is exported by gin/json package. Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent // NewDecoder is exported by gin/json package. From 057f63b1bb1cca059173363d10c3de9512ee1110 Mon Sep 17 00:00:00 2001 From: Riverside Date: Tue, 5 Mar 2019 09:41:37 +0800 Subject: [PATCH 327/912] spell check (#1796) * spell check * variable path collides with imported package name * spell check --- errors.go | 2 +- gin.go | 18 +++++++++--------- recovery.go | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/errors.go b/errors.go index ab13ca6..6070ff5 100644 --- a/errors.go +++ b/errors.go @@ -53,7 +53,7 @@ func (msg *Error) SetMeta(data interface{}) *Error { return msg } -// JSON creates a properly formated JSON +// JSON creates a properly formatted JSON func (msg *Error) JSON() interface{} { json := H{} if msg.Meta != nil { diff --git a/gin.go b/gin.go index e28e957..2d24092 100644 --- a/gin.go +++ b/gin.go @@ -225,7 +225,7 @@ func (engine *Engine) NoMethod(handlers ...HandlerFunc) { engine.rebuild405Handlers() } -// Use attachs a global middleware to the router. ie. the middleware attached though Use() will be +// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { @@ -366,10 +366,10 @@ func (engine *Engine) HandleContext(c *Context) { func (engine *Engine) handleHTTPRequest(c *Context) { httpMethod := c.Request.Method - path := c.Request.URL.Path + rPath := c.Request.URL.Path unescape := false if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { - path = c.Request.URL.RawPath + rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } @@ -381,7 +381,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { } root := t[i].root // Find route in tree - handlers, params, tsr := root.getValue(path, c.Params, unescape) + handlers, params, tsr := root.getValue(rPath, c.Params, unescape) if handlers != nil { c.handlers = handlers c.Params = params @@ -389,7 +389,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { c.writermem.WriteHeaderNow() return } - if httpMethod != "CONNECT" && path != "/" { + if httpMethod != "CONNECT" && rPath != "/" { if tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return @@ -406,7 +406,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { if tree.method == httpMethod { continue } - if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { + if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return @@ -459,15 +459,15 @@ func redirectTrailingSlash(c *Context) { func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { req := c.Request - path := req.URL.Path + rPath := req.URL.Path - if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(path), trailingSlash); ok { + if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok { code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { code = http.StatusTemporaryRedirect } req.URL.Path = string(fixedPath) - debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) + debugPrint("redirecting request %d: %s --> %s", code, rPath, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) c.writermem.WriteHeaderNow() return true diff --git a/recovery.go b/recovery.go index 0e35968..9e893e1 100644 --- a/recovery.go +++ b/recovery.go @@ -52,12 +52,12 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { } if logger != nil { stack := stack(3) - httprequest, _ := httputil.DumpRequest(c.Request, false) + httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { - logger.Printf("%s\n%s%s", err, string(httprequest), reset) + logger.Printf("%s\n%s%s", err, string(httpRequest), reset) } else if IsDebugging() { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", - timeFormat(time.Now()), string(httprequest), err, stack, reset) + timeFormat(time.Now()), string(httpRequest), err, stack, reset) } else { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", timeFormat(time.Now()), err, stack, reset) @@ -128,8 +128,8 @@ func function(pc uintptr) []byte { // *T.ptrmethod // Also the package path might contains dot (e.g. code.google.com/...), // so first eliminate the path prefix - if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 { - name = name[lastslash+1:] + if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { + name = name[lastSlash+1:] } if period := bytes.Index(name, dot); period >= 0 { name = name[period+1:] From 3dc247893e9772d0b95c47f4b32dad3d28feb488 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Wed, 6 Mar 2019 20:47:31 -0500 Subject: [PATCH 328/912] make context.Keys available as LogFormatterParams (#1779) * make context available as LogFormatterParams * pass context Keys to LogFormatterParams * update logger test to check for Key param --- logger.go | 3 +++ logger_test.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/logger.go b/logger.go index dc63997..2ecaed7 100644 --- a/logger.go +++ b/logger.go @@ -66,6 +66,8 @@ type LogFormatterParams struct { IsTerm bool // BodySize is the size of the Response Body BodySize int + // Keys are the keys set on the request's context. + Keys map[string]interface{} } // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. @@ -227,6 +229,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { param := LogFormatterParams{ Request: c.Request, IsTerm: isTerm, + Keys: c.Keys, } // Stop timer diff --git a/logger_test.go b/logger_test.go index c551677..a204177 100644 --- a/logger_test.go +++ b/logger_test.go @@ -181,6 +181,7 @@ func TestLoggerWithFormatter(t *testing.T) { func TestLoggerWithConfigFormatting(t *testing.T) { var gotParam LogFormatterParams + var gotKeys map[string]interface{} buffer := new(bytes.Buffer) router := New() @@ -204,6 +205,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) { router.GET("/example", func(c *Context) { // set dummy ClientIP c.Request.Header.Set("X-Forwarded-For", "20.20.20.20") + gotKeys = c.Keys }) performRequest(router, "GET", "/example?a=100") @@ -223,6 +225,8 @@ func TestLoggerWithConfigFormatting(t *testing.T) { assert.Equal(t, "GET", gotParam.Method) assert.Equal(t, "/example?a=100", gotParam.Path) assert.Empty(t, gotParam.ErrorMessage) + assert.Empty(t, gotParam.ErrorMessage) + assert.Equal(t, gotKeys, gotParam.Keys) } From f7079a861e6db66385aae1d865b9c9bbe10d1bb1 Mon Sep 17 00:00:00 2001 From: Sai Date: Fri, 8 Mar 2019 20:44:39 +0900 Subject: [PATCH 329/912] Delete dupilicated test (#1801) --- logger_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/logger_test.go b/logger_test.go index a204177..3623137 100644 --- a/logger_test.go +++ b/logger_test.go @@ -225,7 +225,6 @@ func TestLoggerWithConfigFormatting(t *testing.T) { assert.Equal(t, "GET", gotParam.Method) assert.Equal(t, "/example?a=100", gotParam.Path) assert.Empty(t, gotParam.ErrorMessage) - assert.Empty(t, gotParam.ErrorMessage) assert.Equal(t, gotKeys, gotParam.Keys) } From 70a0aba3e423246be37462cfdaedd510c26c566e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 8 Mar 2019 23:18:52 +0800 Subject: [PATCH 330/912] travisci: use go module when go11+ (#1800) --- .travis.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0039375..b38adcb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,20 @@ language: go sudo: false -go: - - 1.6.x - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - 1.11.x - - 1.12.x - - master matrix: fast_finish: true include: + - go: 1.6.x + - go: 1.7.x + - go: 1.8.x + - go: 1.9.x + - go: 1.10.x - go: 1.11.x env: GO111MODULE=on - go: 1.12.x env: GO111MODULE=on + - go: master + env: GO111MODULE=on git: depth: 10 From 4a23c4f7b9ced6b8e4476f2e021a61165153b71d Mon Sep 17 00:00:00 2001 From: Sai Date: Mon, 11 Mar 2019 11:52:47 +0900 Subject: [PATCH 331/912] fix #1804 which is caused by calling middleware twice. (#1805) Fix: https://github.com/gin-gonic/gin/issues/1804 `allNoRoute` contains middlewares such as `gin.Logger`, `gin.Recovery`, so on. The correct code is to use `noRoute`. cc: @MetalBreaker --- routergroup.go | 2 +- routes_test.go | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 297d357..a1e6c92 100644 --- a/routergroup.go +++ b/routergroup.go @@ -195,7 +195,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS // Check if file exists and/or if we have permission to access it if _, err := fs.Open(file); err != nil { c.Writer.WriteHeader(http.StatusNotFound) - c.handlers = group.engine.allNoRoute + c.handlers = group.engine.noRoute // Reset index c.index = -1 return diff --git a/routes_test.go b/routes_test.go index a842704..de363a8 100644 --- a/routes_test.go +++ b/routes_test.go @@ -429,7 +429,6 @@ func TestRouterNotFound(t *testing.T) { func TestRouterStaticFSNotFound(t *testing.T) { router := New() - router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) router.NoRoute(func(c *Context) { c.String(404, "non existent") @@ -452,6 +451,27 @@ func TestRouterStaticFSFileNotFound(t *testing.T) { }) } +// Reproduction test for the bug of issue #1805 +func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) { + router := New() + + // Middleware must be called just only once by per request. + middlewareCalledNum := 0 + router.Use(func(c *Context) { + middlewareCalledNum += 1 + }) + + router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) + + // First access + performRequest(router, "GET", "/nonexistent") + assert.Equal(t, 1, middlewareCalledNum) + + // Second access + performRequest(router, "HEAD", "/nonexistent") + assert.Equal(t, 2, middlewareCalledNum) +} + func TestRouteRawPath(t *testing.T) { route := New() route.UseRawPath = true From e5261480fde106ac5385f00fb00f9bc6b4035485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 12 Mar 2019 14:01:12 +0800 Subject: [PATCH 332/912] chore(readme.md): fix invalid link (#1807) --- README.md | 2 +- testdata/assets/console.png | Bin 0 -> 59545 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 testdata/assets/console.png diff --git a/README.md b/README.md index 28ebd74..df5302e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. -![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) +![Gin console logger](testdata/assets/console.png) ## Contents diff --git a/testdata/assets/console.png b/testdata/assets/console.png new file mode 100644 index 0000000000000000000000000000000000000000..7a695718fa31f9b1b42cfa547c16da394378aeae GIT binary patch literal 59545 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 literal 0 HcmV?d00001 From cab0749b4f3fadc8e9a23a5daf7083d3597fdbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 14 Mar 2019 13:23:35 +0800 Subject: [PATCH 333/912] chore: update readme (#1793) * update readme * add multi-language version doc link * add multi-language version doc link * update readme * update * update readme * update readme * update readme --- README.md | 2007 +---------------------------------------------------- 1 file changed, 35 insertions(+), 1972 deletions(-) diff --git a/README.md b/README.md index df5302e..f9c10b2 100644 --- a/README.md +++ b/README.md @@ -13,123 +13,35 @@ Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. -![Gin console logger](testdata/assets/console.png) +**The key features of Gin are:** -## Contents +- Zero allocation router +- Fast +- Middleware support +- Crash-free +- JSON validation +- Routes grouping +- Error management +- Rendering built-in +- Extendable -- [Installation](#installation) -- [Prerequisite](#prerequisite) -- [Quick start](#quick-start) -- [Benchmarks](#benchmarks) -- [Gin v1.stable](#gin-v1-stable) -- [Build with jsoniter](#build-with-jsoniter) -- [API Examples](#api-examples) - - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) - - [Parameters in path](#parameters-in-path) - - [Querystring parameters](#querystring-parameters) - - [Multipart/Urlencoded Form](#multiparturlencoded-form) - - [Another example: query + post form](#another-example-query--post-form) - - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) - - [Upload files](#upload-files) - - [Grouping routes](#grouping-routes) - - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - - [Using middleware](#using-middleware) - - [How to write log file](#how-to-write-log-file) - - [Custom Log Format](#custom-log-format) - - [Model binding and validation](#model-binding-and-validation) - - [Custom Validators](#custom-validators) - - [Only Bind Query String](#only-bind-query-string) - - [Bind Query String or Post Data](#bind-query-string-or-post-data) - - [Bind Uri](#bind-uri) - - [Bind HTML checkboxes](#bind-html-checkboxes) - - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) - - [JSONP rendering](#jsonp) - - [Serving static files](#serving-static-files) - - [Serving data from reader](#serving-data-from-reader) - - [HTML rendering](#html-rendering) - - [Multitemplate](#multitemplate) - - [Redirects](#redirects) - - [Custom Middleware](#custom-middleware) - - [Using BasicAuth() middleware](#using-basicauth-middleware) - - [Goroutines inside a middleware](#goroutines-inside-a-middleware) - - [Custom HTTP configuration](#custom-http-configuration) - - [Support Let's Encrypt](#support-lets-encrypt) - - [Run multiple service using Gin](#run-multiple-service-using-gin) - - [Graceful restart or stop](#graceful-restart-or-stop) - - [Build a single binary with templates](#build-a-single-binary-with-templates) - - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - - [http2 server push](#http2-server-push) - - [Define format for the log of routes](#define-format-for-the-log-of-routes) - - [Set and get a cookie](#set-and-get-a-cookie) -- [Testing](#testing) -- [Users](#users) +For more feature details, please see the [Gin website introduction](https://gin-gonic.com/docs/introduction/). -## Installation +## Getting started -To install Gin package, you need to install Go and set your Go workspace first. +### Getting Gin -1. Download and install it: +The first need [Go](https://golang.org/) installed (version 1.6+ is required), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin ``` -2. Import it in your code: +For more installation guides such as vendor tool, please check out [Gin quickstart](https://gin-gonic.com/docs/quickstart/). -```go -import "github.com/gin-gonic/gin" -``` - -3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. - -```go -import "net/http" -``` - -### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) - -1. `go get` govendor - -```sh -$ go get github.com/kardianos/govendor -``` -2. Create your project folder and `cd` inside - -```sh -$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" -``` - -3. Vendor init your project and add gin +### Running Gin -```sh -$ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.3 -``` - -4. Copy a starting template inside your project - -```sh -$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go -``` - -5. Run your project - -```sh -$ go run main.go -``` - -## Prerequisite - -Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. - -## Quick start - -```sh -# assume the following codes in example.go file -$ cat example.go -``` +First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`: ```go package main @@ -147,6 +59,8 @@ func main() { } ``` +And use the Go command to run the demo: + ``` # run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go @@ -154,9 +68,7 @@ $ go run example.go ## Benchmarks -Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) - -[See all benchmarks](/BENCHMARKS.md) +Please see all benchmarks details from [Gin website](https://gin-gonic.com/docs/benchmarks/). Benchmark name | (1) | (2) | (3) | (4) --------------------------------------------|-----------:|------------:|-----------:|---------: @@ -193,1879 +105,30 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better -## Gin v1. stable - -- [x] Zero allocation router. -- [x] Still the fastest http router and framework. From routing to writing. -- [x] Complete suite of unit tests -- [x] Battle tested -- [x] API frozen, new releases will not break your code. - -## Build with [jsoniter](https://github.com/json-iterator/go) - -Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. - -```sh -$ go build -tags=jsoniter . -``` - -## API Examples - -You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). - -### Using GET, POST, PUT, PATCH, DELETE and OPTIONS - -```go -func main() { - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/someGet", getting) - router.POST("/somePost", posting) - router.PUT("/somePut", putting) - router.DELETE("/someDelete", deleting) - router.PATCH("/somePatch", patching) - router.HEAD("/someHead", head) - router.OPTIONS("/someOptions", options) - - // By default it serves on :8080 unless a - // PORT environment variable was defined. - router.Run() - // router.Run(":3000") for a hard coded port -} -``` - -### Parameters in path - -```go -func main() { - router := gin.Default() - - // This handler will match /user/john but will not match /user/ or /user - router.GET("/user/:name", func(c *gin.Context) { - name := c.Param("name") - c.String(http.StatusOK, "Hello %s", name) - }) - - // However, this one will match /user/john/ and also /user/john/send - // If no other routers match /user/john, it will redirect to /user/john/ - router.GET("/user/:name/*action", func(c *gin.Context) { - name := c.Param("name") - action := c.Param("action") - message := name + " is " + action - c.String(http.StatusOK, message) - }) - - router.Run(":8080") -} -``` - -### Querystring parameters - -```go -func main() { - router := gin.Default() - - // Query string parameters are parsed using the existing underlying request object. - // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe - router.GET("/welcome", func(c *gin.Context) { - firstname := c.DefaultQuery("firstname", "Guest") - lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") - - c.String(http.StatusOK, "Hello %s %s", firstname, lastname) - }) - router.Run(":8080") -} -``` - -### Multipart/Urlencoded Form - -```go -func main() { - router := gin.Default() - - router.POST("/form_post", func(c *gin.Context) { - message := c.PostForm("message") - nick := c.DefaultPostForm("nick", "anonymous") - - c.JSON(200, gin.H{ - "status": "posted", - "message": message, - "nick": nick, - }) - }) - router.Run(":8080") -} -``` - -### Another example: query + post form - -``` -POST /post?id=1234&page=1 HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -name=manu&message=this_is_great -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - id := c.Query("id") - page := c.DefaultQuery("page", "0") - name := c.PostForm("name") - message := c.PostForm("message") - - fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) - }) - router.Run(":8080") -} -``` - -``` -id: 1234; page: 1; name: manu; message: this_is_great -``` - -### Map as querystring or postform parameters - -``` -POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -names[first]=thinkerou&names[second]=tianou -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - ids := c.QueryMap("ids") - names := c.PostFormMap("names") - - fmt.Printf("ids: %v; names: %v", ids, names) - }) - router.Run(":8080") -} -``` - -``` -ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] -``` - -### Upload files - -#### Single file - -References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single). - -`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) - -> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. - -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - // router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // single file - file, _ := c.FormFile("file") - log.Println(file.Filename) - - // Upload the file to specific dst. - // c.SaveUploadedFile(file, dst) - - c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) - }) - router.Run(":8080") -} -``` - -How to `curl`: +## Middlewares -```bash -curl -X POST http://localhost:8080/upload \ - -F "file=@/Users/appleboy/test.zip" \ - -H "Content-Type: multipart/form-data" -``` - -#### Multiple files - -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). - -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - // router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // Multipart form - form, _ := c.MultipartForm() - files := form.File["upload[]"] - - for _, file := range files { - log.Println(file.Filename) - - // Upload the file to specific dst. - // c.SaveUploadedFile(file, dst) - } - c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) - }) - router.Run(":8080") -} -``` - -How to `curl`: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "upload[]=@/Users/appleboy/test1.zip" \ - -F "upload[]=@/Users/appleboy/test2.zip" \ - -H "Content-Type: multipart/form-data" -``` - -### Grouping routes - -```go -func main() { - router := gin.Default() - - // Simple group: v1 - v1 := router.Group("/v1") - { - v1.POST("/login", loginEndpoint) - v1.POST("/submit", submitEndpoint) - v1.POST("/read", readEndpoint) - } - - // Simple group: v2 - v2 := router.Group("/v2") - { - v2.POST("/login", loginEndpoint) - v2.POST("/submit", submitEndpoint) - v2.POST("/read", readEndpoint) - } - - router.Run(":8080") -} -``` - -### Blank Gin without middleware by default - -Use - -```go -r := gin.New() -``` - -instead of - -```go -// Default With the Logger and Recovery middleware already attached -r := gin.Default() -``` - - -### Using middleware -```go -func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. - // By default gin.DefaultWriter = os.Stdout - r.Use(gin.Logger()) - - // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.Recovery()) - - // Per route middleware, you can add as many as you desire. - r.GET("/benchmark", MyBenchLogger(), benchEndpoint) - - // Authorization group - // authorized := r.Group("/", AuthRequired()) - // exactly the same as: - authorized := r.Group("/") - // per group middleware! in this case we use the custom created - // AuthRequired() middleware just in the "authorized" group. - authorized.Use(AuthRequired()) - { - authorized.POST("/login", loginEndpoint) - authorized.POST("/submit", submitEndpoint) - authorized.POST("/read", readEndpoint) - - // nested group - testing := authorized.Group("testing") - testing.GET("/analytics", analyticsEndpoint) - } - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### How to write log file -```go -func main() { - // Disable Console Color, you don't need console color when writing the logs to file. - gin.DisableConsoleColor() - - // Logging to a file. - f, _ := os.Create("gin.log") - gin.DefaultWriter = io.MultiWriter(f) - - // Use the following code if you need to write the logs to file and console at the same time. - // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) - - router := gin.Default() - router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - -    router.Run(":8080") -} -``` - -### Custom Log Format -```go -func main() { - router := gin.New() - - // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter - // By default gin.DefaultWriter = os.Stdout - router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { - - // your custom format - return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", - param.ClientIP, - param.TimeStamp.Format(time.RFC1123), - param.Method, - param.Path, - param.Request.Proto, - param.StatusCode, - param.Latency, - param.Request.UserAgent(), - param.ErrorMessage, - ) - })) - router.Use(gin.Recovery()) - - router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - router.Run(":8080") -} -``` - -**Sample Output** -``` -::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " -``` - -### Controlling Log output coloring - -By default, logs output on console should be colorized depending on the detected TTY. - -Never colorize logs: - -```go -func main() { - // Disable log's color - gin.DisableConsoleColor() - - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - router.Run(":8080") -} -``` - -Always colorize logs: - -```go -func main() { - // Force log's color - gin.ForceConsoleColor() - - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - router.Run(":8080") -} -``` - -### Model binding and validation - -To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). - -Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags). - -Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. - -Also, Gin provides two sets of methods for binding: -- **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` - - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. -- **Type** - Should bind - - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML` - - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. - -When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. - -You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned. - -```go -// Binding from JSON -type Login struct { - User string `form:"user" json:"user" xml:"user" binding:"required"` - Password string `form:"password" json:"password" xml:"password" binding:"required"` -} - -func main() { - router := gin.Default() - - // Example for binding JSON ({"user": "manu", "password": "123"}) - router.POST("/loginJSON", func(c *gin.Context) { - var json Login - if err := c.ShouldBindJSON(&json); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if json.User != "manu" || json.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding XML ( - // - // - // user - // 123 - // ) - router.POST("/loginXML", func(c *gin.Context) { - var xml Login - if err := c.ShouldBindXML(&xml); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if xml.User != "manu" || xml.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) +You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). - // Example for binding a HTML form (user=manu&password=123) - router.POST("/loginForm", func(c *gin.Context) { - var form Login - // This will infer what binder to use depending on the content-type header. - if err := c.ShouldBind(&form); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if form.User != "manu" || form.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") -} -``` - -**Sample request** -```shell -$ curl -v -X POST \ - http://localhost:8080/loginJSON \ - -H 'content-type: application/json' \ - -d '{ "user": "manu" }' -> POST /loginJSON HTTP/1.1 -> Host: localhost:8080 -> User-Agent: curl/7.51.0 -> Accept: */* -> content-type: application/json -> Content-Length: 18 -> -* upload completely sent off: 18 out of 18 bytes -< HTTP/1.1 400 Bad Request -< Content-Type: application/json; charset=utf-8 -< Date: Fri, 04 Aug 2017 03:51:31 GMT -< Content-Length: 100 -< -{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} -``` - -**Skip validate** - -When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. - -### Custom Validators - -It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). - -```go -package main - -import ( - "net/http" - "reflect" - "time" - - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - "gopkg.in/go-playground/validator.v8" -) - -// Booking contains binded and validated data. -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") -} +## Documentation -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"} -``` +All documentation is available on the Gin website. -[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. -See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. +- [English](https://gin-gonic.com/docs/) +- [简体中文](https://gin-gonic.com/zh-cn/docs/) +- [繁體中文](https://gin-gonic.com/zh-tw/docs/) +- [にほんご](https://gin-gonic.com/ja/docs/) -### Only Bind Query String +## Examples -`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). +A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. -```go -package main +## Users -import ( - "log" +[Gin website](https://gin-gonic.com/docs/users/) lists some awesome projects made with Gin web framework. - "github.com/gin-gonic/gin" -) +## Contributing -type Person struct { - Name string `form:"name"` - Address string `form:"address"` -} - -func main() { - route := gin.Default() - route.Any("/testing", startPage) - route.Run(":8085") -} - -func startPage(c *gin.Context) { - var person Person - if c.ShouldBindQuery(&person) == nil { - log.Println("====== Only Bind By Query String ======") - log.Println(person.Name) - log.Println(person.Address) - } - c.String(200, "Success") -} - -``` - -### Bind Query String or Post Data - -See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). - -```go -package main - -import ( - "log" - "time" - - "github.com/gin-gonic/gin" -) - -type Person struct { - Name string `form:"name"` - Address string `form:"address"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` -} - -func main() { - route := gin.Default() - route.GET("/testing", startPage) - route.Run(":8085") -} - -func startPage(c *gin.Context) { - var person Person - // If `GET`, only `Form` binding engine (`query`) used. - // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). - // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 - if c.ShouldBind(&person) == nil { - log.Println(person.Name) - log.Println(person.Address) - log.Println(person.Birthday) - } - - c.String(200, "Success") -} -``` - -Test it with: -```sh -$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" -``` - -### Bind Uri - -See the [detail information](https://github.com/gin-gonic/gin/issues/846). - -```go -package main - -import "github.com/gin-gonic/gin" - -type Person struct { - ID string `uri:"id" binding:"required,uuid"` - Name string `uri:"name" binding:"required"` -} - -func main() { - route := gin.Default() - route.GET("/:name/:id", func(c *gin.Context) { - var person Person - if err := c.ShouldBindUri(&person); err != nil { - c.JSON(400, gin.H{"msg": err}) - return - } - c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) - }) - route.Run(":8088") -} -``` - -Test it with: -```sh -$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 -$ curl -v localhost:8088/thinkerou/not-uuid -``` - -### Bind HTML checkboxes - -See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) - -main.go - -```go -... - -type myForm struct { - Colors []string `form:"colors[]"` -} - -... - -func formHandler(c *gin.Context) { - var fakeForm myForm - c.ShouldBind(&fakeForm) - c.JSON(200, gin.H{"color": fakeForm.Colors}) -} - -... - -``` - -form.html - -```html -
-

Check some colors

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

- {{ .title }} -

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

- {{ .title }} -

-

Using posts/index.tmpl

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

- {{ .title }} -

-

Using users/index.tmpl

- -{{ end }} -``` - -#### Custom Template renderer - -You can also use your own html template render - -```go -import "html/template" - -func main() { - router := gin.Default() - html := template.Must(template.ParseFiles("file1", "file2")) - router.SetHTMLTemplate(html) - router.Run(":8080") -} -``` - -#### Custom Delimiters - -You may use custom delims - -```go - r := gin.Default() - r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates") -``` - -#### Custom Template Funcs - -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). - -main.go - -```go -import ( - "fmt" - "html/template" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -func formatAsDate(t time.Time) string { - year, month, day := t.Date() - return fmt.Sprintf("%d%02d/%02d", year, month, day) -} - -func main() { - router := gin.Default() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("./testdata/template/raw.tmpl") - - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - - router.Run(":8080") -} - -``` - -raw.tmpl - -```html -Date: {[{.now | formatAsDate}]} -``` - -Result: -``` -Date: 2017/07/01 -``` - -### Multitemplate - -Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. - -### Redirects - -Issuing a HTTP redirect is easy. Both internal and external locations are supported. - -```go -r.GET("/test", func(c *gin.Context) { - c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") -}) -``` - - -Issuing a Router redirect, use `HandleContext` like below. - -``` go -r.GET("/test", func(c *gin.Context) { - c.Request.URL.Path = "/test2" - r.HandleContext(c) -}) -r.GET("/test2", func(c *gin.Context) { - c.JSON(200, gin.H{"hello": "world"}) -}) -``` - - -### Custom Middleware - -```go -func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - t := time.Now() - - // Set example variable - c.Set("example", "12345") - - // before request - - c.Next() - - // after request - latency := time.Since(t) - log.Print(latency) - - // access the status we are sending - status := c.Writer.Status() - log.Println(status) - } -} - -func main() { - r := gin.New() - r.Use(Logger()) - - r.GET("/test", func(c *gin.Context) { - example := c.MustGet("example").(string) - - // it would print: "12345" - log.Println(example) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Using BasicAuth() middleware - -```go -// simulate some private data -var secrets = gin.H{ - "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, - "austin": gin.H{"email": "austin@example.com", "phone": "666"}, - "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, -} - -func main() { - r := gin.Default() - - // Group using gin.BasicAuth() middleware - // gin.Accounts is a shortcut for map[string]string - authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ - "foo": "bar", - "austin": "1234", - "lena": "hello2", - "manu": "4321", - })) - - // /admin/secrets endpoint - // hit "localhost:8080/admin/secrets - authorized.GET("/secrets", func(c *gin.Context) { - // get user, it was set by the BasicAuth middleware - user := c.MustGet(gin.AuthUserKey).(string) - if secret, ok := secrets[user]; ok { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) - } else { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) - } - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Goroutines inside a middleware - -When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. - -```go -func main() { - r := gin.Default() - - r.GET("/long_async", func(c *gin.Context) { - // create copy to be used inside the goroutine - cCp := c.Copy() - go func() { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // note that you are using the copied context "cCp", IMPORTANT - log.Println("Done! in path " + cCp.Request.URL.Path) - }() - }) - - r.GET("/long_sync", func(c *gin.Context) { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // since we are NOT using a goroutine, we do not have to copy the context - log.Println("Done! in path " + c.Request.URL.Path) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Custom HTTP configuration - -Use `http.ListenAndServe()` directly, like this: - -```go -func main() { - router := gin.Default() - http.ListenAndServe(":8080", router) -} -``` -or - -```go -func main() { - router := gin.Default() - - s := &http.Server{ - Addr: ":8080", - Handler: router, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - s.ListenAndServe() -} -``` - -### Support Let's Encrypt - -example for 1-line LetsEncrypt HTTPS servers. - -```go -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - log.Fatal(autotls.Run(r, "example1.com", "example2.com")) -} -``` - -example for custom autocert manager. - -```go -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" - "golang.org/x/crypto/acme/autocert" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), - Cache: autocert.DirCache("/var/www/.cache"), - } - - log.Fatal(autotls.RunWithManager(r, &m)) -} -``` - -### Run multiple service using Gin - -See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: - -```go -package main - -import ( - "log" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "golang.org/x/sync/errgroup" -) - -var ( - g errgroup.Group -) - -func router01() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 01", - }, - ) - }) - - return e -} - -func router02() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 02", - }, - ) - }) - - return e -} - -func main() { - server01 := &http.Server{ - Addr: ":8080", - Handler: router01(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - server02 := &http.Server{ - Addr: ":8081", - Handler: router02(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - g.Go(func() error { - return server01.ListenAndServe() - }) - - g.Go(func() error { - return server02.ListenAndServe() - }) - - if err := g.Wait(); err != nil { - log.Fatal(err) - } -} -``` - -### Graceful restart or stop - -Do you want to graceful restart or stop your web server? -There are some ways this can be done. - -We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. - -```go -router := gin.Default() -router.GET("/", handler) -// [...] -endless.ListenAndServe(":4242", router) -``` - -An alternative to endless: - -* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. -* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. -* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. - -If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown) example with gin. - -```go -// +build go1.8 - -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - router.GET("/", func(c *gin.Context) { - time.Sleep(5 * time.Second) - c.String(http.StatusOK, "Welcome Gin Server") - }) - - srv := &http.Server{ - Addr: ":8080", - Handler: router, - } - - go func() { - // service connections - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) - } - }() - - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 5 seconds. - quit := make(chan os.Signal) - // kill (no param) default send syscanll.SIGTERM - // kill -2 is syscall.SIGINT - // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Println("Shutdown Server ...") - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server Shutdown:", err) - } - // catching ctx.Done(). timeout of 5 seconds. - select { - case <-ctx.Done(): - log.Println("timeout of 5 seconds.") - } - 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 `https://github.com/gin-gonic/examples/tree/master/assets-in-binary` directory. - -### Bind form-data request with custom struct - -The follow example using custom struct: - -```go -type StructA struct { - FieldA string `form:"field_a"` -} - -type StructB struct { - NestedStruct StructA - FieldB string `form:"field_b"` -} - -type StructC struct { - NestedStructPointer *StructA - FieldC string `form:"field_c"` -} - -type StructD struct { - NestedAnonyStruct struct { - FieldX string `form:"field_x"` - } - FieldD string `form:"field_d"` -} - -func GetDataB(c *gin.Context) { - var b StructB - c.Bind(&b) - c.JSON(200, gin.H{ - "a": b.NestedStruct, - "b": b.FieldB, - }) -} - -func GetDataC(c *gin.Context) { - var b StructC - c.Bind(&b) - c.JSON(200, gin.H{ - "a": b.NestedStructPointer, - "c": b.FieldC, - }) -} - -func GetDataD(c *gin.Context) { - var b StructD - c.Bind(&b) - c.JSON(200, gin.H{ - "x": b.NestedAnonyStruct, - "d": b.FieldD, - }) -} - -func main() { - r := gin.Default() - r.GET("/getb", GetDataB) - r.GET("/getc", GetDataC) - r.GET("/getd", GetDataD) - - r.Run() -} -``` - -Using the command `curl` command result: - -``` -$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" -{"a":{"FieldA":"hello"},"b":"world"} -$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" -{"a":{"FieldA":"hello"},"c":"world"} -$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" -{"d":"world","x":{"FieldX":"hello"}} -``` - -### Try to bind body into different structs - -The normal methods for binding request body consumes `c.Request.Body` and they -cannot be called multiple times. - -```go -type formA struct { - Foo string `json:"foo" xml:"foo" binding:"required"` -} - -type formB struct { - Bar string `json:"bar" xml:"bar" binding:"required"` -} - -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // This c.ShouldBind consumes c.Request.Body and it cannot be reused. - if errA := c.ShouldBind(&objA); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // Always an error is occurred by this because c.Request.Body is EOF now. - } else if errB := c.ShouldBind(&objB); errB == nil { - c.String(http.StatusOK, `the body should be formB`) - } else { - ... - } -} -``` - -For this, you can use `c.ShouldBindBodyWith`. - -```go -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // This reads c.Request.Body and stores the result into the context. - if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // At this time, it reuses body stored in the context. - } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { - c.String(http.StatusOK, `the body should be formB JSON`) - // And it can accepts other formats - } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { - c.String(http.StatusOK, `the body should be formB XML`) - } else { - ... - } -} -``` - -* `c.ShouldBindBodyWith` stores body into the context before binding. This has -a slight impact to performance, so you should not use this method if you are -enough to call binding at once. -* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, -`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, -can be called by `c.ShouldBind()` multiple times without any damage to -performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). - -### http2 server push - -http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. - -```go -package main - -import ( - "html/template" - "log" - - "github.com/gin-gonic/gin" -) - -var html = template.Must(template.New("https").Parse(` - - - Https Test - - - -

Welcome, Ginner!

- - -`)) - -func main() { - r := gin.Default() - r.Static("/assets", "./assets") - r.SetHTMLTemplate(html) - - r.GET("/", func(c *gin.Context) { - if pusher := c.Writer.Pusher(); pusher != nil { - // use pusher.Push() to do server push - if err := pusher.Push("/assets/app.js", nil); err != nil { - log.Printf("Failed to push: %v", err) - } - } - c.HTML(200, "https", gin.H{ - "status": "success", - }) - }) - - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") -} -``` - -### Define format for the log of routes - -The default log of routes is: -``` -[GIN-debug] POST /foo --> main.main.func1 (3 handlers) -[GIN-debug] GET /bar --> main.main.func2 (3 handlers) -[GIN-debug] GET /status --> main.main.func3 (3 handlers) -``` - -If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. -In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. -```go -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { - log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) - } - - r.POST("/foo", func(c *gin.Context) { - c.JSON(http.StatusOK, "foo") - }) - - r.GET("/bar", func(c *gin.Context) { - c.JSON(http.StatusOK, "bar") - }) - - r.GET("/status", func(c *gin.Context) { - c.JSON(http.StatusOK, "ok") - }) - - // Listen and Server in http://0.0.0.0:8080 - r.Run() -} -``` - -### Set and get a cookie - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - - router.GET("/cookie", func(c *gin.Context) { - - cookie, err := c.Cookie("gin_cookie") - - if err != nil { - cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) - } - - fmt.Printf("Cookie value: %s \n", cookie) - }) - - router.Run() -} -``` - - -## Testing - -The `net/http/httptest` package is preferable way for HTTP testing. - -```go -package main - -func setupRouter() *gin.Engine { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - return r -} - -func main() { - r := setupRouter() - r.Run(":8080") -} -``` - -Test for code example above: - -```go -package main - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPingRoute(t *testing.T) { - router := setupRouter() - - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/ping", nil) - router.ServeHTTP(w, req) - - assert.Equal(t, 200, w.Code) - assert.Equal(t, "pong", w.Body.String()) -} -``` - -## Users +Gin is the work of hundreds of contributors. We appreciate your help! -Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. +Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. -* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. -* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. -* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. -* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. From 483f828bce15ff82fdec7414c32ee9b3f017b5ca Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Thu, 14 Mar 2019 08:34:56 +0300 Subject: [PATCH 334/912] add support arrays on mapping (#1797) * add support arrays on mapping * not allow default value on array mapping --- binding/binding_test.go | 28 ++++++++++++++++++++++++++++ binding/form_mapping.go | 23 ++++++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 5ae8795..b265af3 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1430,3 +1430,31 @@ func TestBindingTimeDuration(t *testing.T) { err = Form.Bind(req, &s) assert.Error(t, err) } + +func TestBindingArray(t *testing.T) { + var s struct { + Nums [2]int `form:"nums,default=4"` + } + + // default + req := formPostRequest("", "") + err := Form.Bind(req, &s) + assert.Error(t, err) + assert.Equal(t, [2]int{0, 0}, s.Nums) + + // ok + req = formPostRequest("", "nums=3&nums=8") + err = Form.Bind(req, &s) + assert.NoError(t, err) + assert.Equal(t, [2]int{3, 8}, s.Nums) + + // not enough vals + req = formPostRequest("", "nums=3") + err = Form.Bind(req, &s) + assert.Error(t, err) + + // error + req = formPostRequest("", "nums=3&nums=wrong") + err = Form.Bind(req, &s) + assert.Error(t, err) +} diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 87edfbb..ba9d2c4 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -6,6 +6,7 @@ package binding import ( "errors" + "fmt" "reflect" "strconv" "strings" @@ -118,6 +119,14 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri vs = []string{defaultValue} } return true, setSlice(vs, value, field) + case reflect.Array: + if !ok { + vs = []string{defaultValue} + } + if len(vs) != value.Len() { + return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) + } + return true, setArray(vs, value, field) default: var val string if !ok { @@ -256,14 +265,22 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val return nil } -func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { - slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) +func setArray(vals []string, value reflect.Value, field reflect.StructField) error { for i, s := range vals { - err := setWithProperType(s, slice.Index(i), field) + err := setWithProperType(s, value.Index(i), field) if err != nil { return err } } + return nil +} + +func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { + slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) + err := setArray(vals, slice, field) + if err != nil { + return err + } value.Set(slice) return nil } From 242a2622c839bf883c415fd088d24fec8727fb23 Mon Sep 17 00:00:00 2001 From: Sai Date: Thu, 14 Mar 2019 17:26:51 +0900 Subject: [PATCH 335/912] Fix Japanese text hiragana -> kanji (#1812) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9c10b2..b46c563 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ All documentation is available on the Gin website. - [English](https://gin-gonic.com/docs/) - [简体中文](https://gin-gonic.com/zh-cn/docs/) - [繁體中文](https://gin-gonic.com/zh-tw/docs/) -- [にほんご](https://gin-gonic.com/ja/docs/) +- [日本語](https://gin-gonic.com/ja/docs/) ## Examples From 05b5c3ba7495fb3cd737cad8b35e62e0862ed1c2 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 15 Mar 2019 15:39:34 +0800 Subject: [PATCH 336/912] Doc: fix gin example notice syntax (#1814) --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index b02deae..bfebc6c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,3 @@ # Gin Examples -⚠️ **NOTICE:** All gin examples has moved as alone repository to [here](https://github.com/gin-gonic/examples). +⚠️ **NOTICE:** All gin examples have been moved as standalone repository to [here](https://github.com/gin-gonic/examples). From bcf36ade9f763b875bd781ad26cdb0549349c5f8 Mon Sep 17 00:00:00 2001 From: sekky0905 <20237968+sekky0905@users.noreply.github.com> Date: Sat, 16 Mar 2019 17:09:10 +0900 Subject: [PATCH 337/912] Remove sudo setting from travis.yml (#1816) --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b38adcb..2fd9c8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: go -sudo: false matrix: fast_finish: true From c16bfa7949c6ca59c049d20df507d24b1f2ec629 Mon Sep 17 00:00:00 2001 From: Boyi Wu Date: Mon, 18 Mar 2019 10:16:34 +0800 Subject: [PATCH 338/912] update for supporting file binding (#1264) update for supporting multipart form and file binding example: ``` type PhoptUploadForm struct { imgData *multipart.FileHeader `form:"img_data" binding:"required"` ProjectID string `form:"project_id" binding:"required"` Description string `form:"description binding:"required"` } ``` ref: https://github.com/gin-gonic/gin/issues/1263 --- binding/binding.go | 4 +- binding/binding_test.go | 94 ++++++++++++++++++++++++++++++++++++++++- binding/form.go | 5 +++ binding/form_mapping.go | 29 +++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 26d71c9..520c510 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -98,7 +98,9 @@ func Default(method, contentType string) Binding { return MsgPack case MIMEYAML: return YAML - default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: + case MIMEMultipartPOSTForm: + return FormMultipart + default: // case MIMEPOSTForm: return Form } } diff --git a/binding/binding_test.go b/binding/binding_test.go index b265af3..ee78822 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -8,9 +8,11 @@ import ( "bytes" "encoding/json" "errors" + "io" "io/ioutil" "mime/multipart" "net/http" + "os" "strconv" "strings" "testing" @@ -31,6 +33,18 @@ type FooBarStruct struct { Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } +type FooBarFileStruct struct { + FooBarStruct + File *multipart.FileHeader `form:"file" binding:"required"` +} + +type FooBarFileFailStruct struct { + FooBarStruct + File *multipart.FileHeader `invalid_name:"file" binding:"required"` + // for unexport test + data *multipart.FileHeader `form:"data" binding:"required"` +} + type FooDefaultBarStruct struct { FooStruct Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"` @@ -187,8 +201,8 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) - assert.Equal(t, Form, Default("POST", MIMEMultipartPOSTForm)) - assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm)) assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) @@ -536,6 +550,54 @@ func createFormPostRequestForMapFail(t *testing.T) *http.Request { return req } +func createFormFilesMultipartRequest(t *testing.T) *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) + + f, err := os.Open("form.go") + assert.NoError(t, err) + defer f.Close() + fw, err1 := mw.CreateFormFile("file", "form.go") + assert.NoError(t, err1) + io.Copy(fw, f) + + req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + assert.NoError(t, err2) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + + return req +} + +func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) + + f, err := os.Open("form.go") + assert.NoError(t, err) + defer f.Close() + fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go") + assert.NoError(t, err1) + io.Copy(fw, f) + + req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + assert.NoError(t, err2) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + + return req +} + func createFormMultipartRequest(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) @@ -613,6 +675,34 @@ func TestBindingFormPostForMapFail(t *testing.T) { assert.Error(t, err) } +func TestBindingFormFilesMultipart(t *testing.T) { + req := createFormFilesMultipartRequest(t) + var obj FooBarFileStruct + FormMultipart.Bind(req, &obj) + + // file from os + f, _ := os.Open("form.go") + defer f.Close() + fileActual, _ := ioutil.ReadAll(f) + + // file from multipart + mf, _ := obj.File.Open() + defer mf.Close() + fileExpect, _ := ioutil.ReadAll(mf) + + assert.Equal(t, FormMultipart.Name(), "multipart/form-data") + assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, fileExpect, fileActual) +} + +func TestBindingFormFilesMultipartFail(t *testing.T) { + req := createFormFilesMultipartRequestFail(t) + var obj FooBarFileFailStruct + err := FormMultipart.Bind(req, &obj) + assert.Error(t, err) +} + func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest(t) var obj FooBarStruct diff --git a/binding/form.go b/binding/form.go index 8955c95..f1f8919 100644 --- a/binding/form.go +++ b/binding/form.go @@ -56,5 +56,10 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { if err := mapForm(obj, req.MultipartForm.Value); err != nil { return err } + + if err := mapFiles(obj, req); err != nil { + return err + } + return validate(obj) } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index ba9d2c4..fc33b1d 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,6 +7,7 @@ package binding import ( "errors" "fmt" + "net/http" "reflect" "strconv" "strings" @@ -15,6 +16,34 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +func mapFiles(ptr interface{}, req *http.Request) error { + typ := reflect.TypeOf(ptr).Elem() + val := reflect.ValueOf(ptr).Elem() + for i := 0; i < typ.NumField(); i++ { + typeField := typ.Field(i) + structField := val.Field(i) + + t := fmt.Sprintf("%s", typeField.Type) + if string(t) != "*multipart.FileHeader" { + continue + } + + inputFieldName := typeField.Tag.Get("form") + if inputFieldName == "" { + inputFieldName = typeField.Name + } + + _, fileHeader, err := req.FormFile(inputFieldName) + if err != nil { + return err + } + + structField.Set(reflect.ValueOf(fileHeader)) + + } + return nil +} + var errUnknownType = errors.New("Unknown type") func mapUri(ptr interface{}, m map[string][]string) error { From b40d4c175c079fa41cda2669c308485175cf2eee Mon Sep 17 00:00:00 2001 From: Sai Date: Mon, 18 Mar 2019 12:12:30 +0900 Subject: [PATCH 339/912] IsTerm flag should not be affected by DisableConsoleColor method. (#1802) * IsTerm flag should not be affected by DisableConsoleColor method. * change public property to private --- logger.go | 49 +++++++++++++++++++++++++++++------------------- logger_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/logger.go b/logger.go index 2ecaed7..198a019 100644 --- a/logger.go +++ b/logger.go @@ -14,17 +14,24 @@ import ( "github.com/mattn/go-isatty" ) +type consoleColorModeValue int + +const ( + autoColor consoleColorModeValue = iota + disableColor + forceColor +) + var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) - disableColor = false - forceColor = false + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) + yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) + cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) + reset = string([]byte{27, 91, 48, 109}) + consoleColorMode = autoColor ) // LoggerConfig defines the config for Logger middleware. @@ -62,8 +69,8 @@ type LogFormatterParams struct { Path string // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string - // IsTerm shows whether does gin's output descriptor refers to a terminal. - IsTerm bool + // isTerm shows whether does gin's output descriptor refers to a terminal. + isTerm bool // BodySize is the size of the Response Body BodySize int // Keys are the keys set on the request's context. @@ -115,10 +122,15 @@ func (p *LogFormatterParams) ResetColor() string { return reset } +// IsOutputColor indicates whether can colors be outputted to the log. +func (p *LogFormatterParams) IsOutputColor() bool { + return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm) +} + // defaultLogFormatter is the default log format function Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { var statusColor, methodColor, resetColor string - if param.IsTerm { + if param.IsOutputColor() { statusColor = param.StatusCodeColor() methodColor = param.MethodColor() resetColor = param.ResetColor() @@ -137,12 +149,12 @@ var defaultLogFormatter = func(param LogFormatterParams) string { // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { - disableColor = true + consoleColorMode = disableColor } // ForceConsoleColor force color output in the console. func ForceConsoleColor() { - forceColor = true + consoleColorMode = forceColor } // ErrorLogger returns a handlerfunc for any error type. @@ -199,9 +211,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { isTerm := true - if w, ok := out.(*os.File); (!ok || - (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) || - disableColor) && !forceColor { + if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || + (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { isTerm = false } @@ -228,7 +239,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { if _, ok := skip[path]; !ok { param := LogFormatterParams{ Request: c.Request, - IsTerm: isTerm, + isTerm: isTerm, Keys: c.Keys, } diff --git a/logger_test.go b/logger_test.go index 3623137..11a859e 100644 --- a/logger_test.go +++ b/logger_test.go @@ -240,7 +240,7 @@ func TestDefaultLogFormatter(t *testing.T) { Method: "GET", Path: "/", ErrorMessage: "", - IsTerm: false, + isTerm: false, } termTrueParam := LogFormatterParams{ @@ -251,7 +251,7 @@ func TestDefaultLogFormatter(t *testing.T) { Method: "GET", Path: "/", ErrorMessage: "", - IsTerm: true, + isTerm: true, } assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) @@ -296,6 +296,39 @@ func TestResetColor(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor()) } +func TestIsOutputColor(t *testing.T) { + // test with isTerm flag true. + p := LogFormatterParams{ + isTerm: true, + } + + consoleColorMode = autoColor + assert.Equal(t, true, p.IsOutputColor()) + + ForceConsoleColor() + assert.Equal(t, true, p.IsOutputColor()) + + DisableConsoleColor() + assert.Equal(t, false, p.IsOutputColor()) + + // test with isTerm flag false. + p = LogFormatterParams{ + isTerm: false, + } + + consoleColorMode = autoColor + assert.Equal(t, false, p.IsOutputColor()) + + ForceConsoleColor() + assert.Equal(t, true, p.IsOutputColor()) + + DisableConsoleColor() + assert.Equal(t, false, p.IsOutputColor()) + + // reset console color mode. + consoleColorMode = autoColor +} + func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) @@ -358,14 +391,20 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { func TestDisableConsoleColor(t *testing.T) { New() - assert.False(t, disableColor) + assert.Equal(t, autoColor, consoleColorMode) DisableConsoleColor() - assert.True(t, disableColor) + assert.Equal(t, disableColor, consoleColorMode) + + // reset console color mode. + consoleColorMode = autoColor } func TestForceConsoleColor(t *testing.T) { New() - assert.False(t, forceColor) + assert.Equal(t, autoColor, consoleColorMode) ForceConsoleColor() - assert.True(t, forceColor) + assert.Equal(t, forceColor, consoleColorMode) + + // reset console color mode. + consoleColorMode = autoColor } From 0c1f3c4e81f6e969a3e465d933faefb5578c54d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 20 Mar 2019 12:07:34 +0800 Subject: [PATCH 340/912] chore: fix invalid link (#1820) --- README.md | 4 +++- doc.go | 2 +- testdata/assets/console.png | Bin 59545 -> 0 bytes 3 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 testdata/assets/console.png diff --git a/README.md b/README.md index b46c563..d3433ed 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ For more feature details, please see the [Gin website introduction](https://gin- ### Getting Gin -The first need [Go](https://golang.org/) installed (version 1.6+ is required), then you can use the below Go command to install Gin. +The first need [Go](https://golang.org/) installed (**version 1.6+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin @@ -111,6 +111,8 @@ You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin ## Documentation +See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. + All documentation is available on the Gin website. - [English](https://gin-gonic.com/docs/) diff --git a/doc.go b/doc.go index 01ac4a9..1bd0386 100644 --- a/doc.go +++ b/doc.go @@ -1,6 +1,6 @@ /* Package gin implements a HTTP web framework called gin. -See https://gin-gonic.github.io/gin/ for more information about gin. +See https://gin-gonic.com/ for more information about gin. */ package gin // import "github.com/gin-gonic/gin" diff --git a/testdata/assets/console.png b/testdata/assets/console.png deleted file mode 100644 index 7a695718fa31f9b1b42cfa547c16da394378aeae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59545 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
?hY(7P?kZ1wD_zt6I%hfC|R7KdZM)`&t88E6Ua# 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 From 1d462bbe3713bc2fea40ed45c80a06ce856d379f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 21 Mar 2019 15:12:06 +0800 Subject: [PATCH 341/912] chore: update ginS (#1822) --- ginS/gins.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index 0f08645..3ce4a6f 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -125,23 +125,35 @@ func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { return engine().Use(middlewares...) } -// Run : The router is attached to a http.Server and starts listening and serving HTTP requests. +// Routes returns a slice of registered routes. +func Routes() gin.RoutesInfo { + return engine().Routes() +} + +// Run attaches to a http.Server and starts listening and serving HTTP requests. // It is a shortcut for http.ListenAndServe(addr, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func Run(addr ...string) (err error) { return engine().Run(addr...) } -// RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests. +// RunTLS attaches to a http.Server and starts listening and serving HTTPS requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func RunTLS(addr, certFile, keyFile string) (err error) { return engine().RunTLS(addr, certFile, keyFile) } -// RunUnix : The router is attached to a http.Server and starts listening and serving HTTP requests +// RunUnix attaches to a http.Server and starts listening and serving HTTP requests // through the specified unix socket (ie. a file) // Note: this method will block the calling goroutine indefinitely unless an error happens. func RunUnix(file string) (err error) { return engine().RunUnix(file) } + +// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests +// through the specified file descriptor. +// Note: thie method will block the calling goroutine indefinitely unless on error happens. +func RunFd(fd int) (err error) { + return engine().RunFd(fd) +} From ce20f107f5dc498ec7489d7739541a25dcd48463 Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Wed, 27 Mar 2019 23:14:00 -0700 Subject: [PATCH 342/912] Truncate Latency precision in long running request (#1830) fixes #1823 --- logger.go | 4 ++++ logger_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/logger.go b/logger.go index 198a019..5ab4639 100644 --- a/logger.go +++ b/logger.go @@ -136,6 +136,10 @@ var defaultLogFormatter = func(param LogFormatterParams) string { resetColor = param.ResetColor() } + if param.Latency > time.Minute { + // Truncate in a golang < 1.8 safe way + param.Latency = param.Latency - param.Latency%time.Second + } return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), statusColor, param.StatusCode, resetColor, diff --git a/logger_test.go b/logger_test.go index 11a859e..56bb3a0 100644 --- a/logger_test.go +++ b/logger_test.go @@ -253,10 +253,34 @@ func TestDefaultLogFormatter(t *testing.T) { ErrorMessage: "", isTerm: true, } + termTrueLongDurationParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Millisecond * 9876543210, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + isTerm: true, + } + + termFalseLongDurationParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Millisecond * 9876543210, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + isTerm: false, + } assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseLongDurationParam)) assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueLongDurationParam)) + } func TestColorForMethod(t *testing.T) { From 2e915f4e5083995154f65a600c86582b5396d02a Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Tue, 2 Apr 2019 04:01:34 +0300 Subject: [PATCH 343/912] refactor(form_mapping.go): mapping multipart request (#1829) * refactor(form_mapping.go): mapping multipart request * add checkers for a types to match with the setter interface * form_mapping.go: rename method name on setter interface, add comments * fix style of comments --- binding/form.go | 34 ++++++++++++++--- binding/form_mapping.go | 84 ++++++++++++++++++++--------------------- 2 files changed, 70 insertions(+), 48 deletions(-) diff --git a/binding/form.go b/binding/form.go index f1f8919..0b28aa8 100644 --- a/binding/form.go +++ b/binding/form.go @@ -4,7 +4,11 @@ package binding -import "net/http" +import ( + "mime/multipart" + "net/http" + "reflect" +) const defaultMemory = 32 * 1024 * 1024 @@ -53,13 +57,33 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseMultipartForm(defaultMemory); err != nil { return err } - if err := mapForm(obj, req.MultipartForm.Value); err != nil { + if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil { return err } - if err := mapFiles(obj, req); err != nil { - return err + return validate(obj) +} + +type multipartRequest http.Request + +var _ setter = (*multipartRequest)(nil) + +var ( + multipartFileHeaderStructType = reflect.TypeOf(multipart.FileHeader{}) +) + +// TrySet tries to set a value by the multipart request with the binding a form file +func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { + if value.Type() == multipartFileHeaderStructType { + _, file, err := (*http.Request)(r).FormFile(key) + if err != nil { + return false, err + } + if file != nil { + value.Set(reflect.ValueOf(*file)) + return true, nil + } } - return validate(obj) + return setByForm(value, field, r.MultipartForm.Value, key, opt) } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index fc33b1d..aaacf6c 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,7 +7,6 @@ package binding import ( "errors" "fmt" - "net/http" "reflect" "strconv" "strings" @@ -16,34 +15,6 @@ import ( "github.com/gin-gonic/gin/internal/json" ) -func mapFiles(ptr interface{}, req *http.Request) error { - typ := reflect.TypeOf(ptr).Elem() - val := reflect.ValueOf(ptr).Elem() - for i := 0; i < typ.NumField(); i++ { - typeField := typ.Field(i) - structField := val.Field(i) - - t := fmt.Sprintf("%s", typeField.Type) - if string(t) != "*multipart.FileHeader" { - continue - } - - inputFieldName := typeField.Tag.Get("form") - if inputFieldName == "" { - inputFieldName = typeField.Name - } - - _, fileHeader, err := req.FormFile(inputFieldName) - if err != nil { - return err - } - - structField.Set(reflect.ValueOf(fileHeader)) - - } - return nil -} - var errUnknownType = errors.New("Unknown type") func mapUri(ptr interface{}, m map[string][]string) error { @@ -57,11 +28,29 @@ func mapForm(ptr interface{}, form map[string][]string) error { var emptyField = reflect.StructField{} func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { - _, err := mapping(reflect.ValueOf(ptr), emptyField, form, tag) + return mappingByPtr(ptr, formSource(form), tag) +} + +// setter tries to set value on a walking by fields of a struct +type setter interface { + TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) +} + +type formSource map[string][]string + +var _ setter = formSource(nil) + +// TrySet tries to set a value by request's form source (like map[string][]string) +func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { + return setByForm(value, field, form, tagValue, opt) +} + +func mappingByPtr(ptr interface{}, setter setter, tag string) error { + _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) return err } -func mapping(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { +func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { var vKind = value.Kind() if vKind == reflect.Ptr { @@ -71,7 +60,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s isNew = true vPtr = reflect.New(value.Type().Elem()) } - isSetted, err := mapping(vPtr.Elem(), field, form, tag) + isSetted, err := mapping(vPtr.Elem(), field, setter, tag) if err != nil { return false, err } @@ -81,7 +70,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s return isSetted, nil } - ok, err := tryToSetValue(value, field, form, tag) + ok, err := tryToSetValue(value, field, setter, tag) if err != nil { return false, err } @@ -97,7 +86,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s if !value.Field(i).CanSet() { continue } - ok, err := mapping(value.Field(i), tValue.Field(i), form, tag) + ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) if err != nil { return false, err } @@ -108,9 +97,14 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s return false, nil } -func tryToSetValue(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { - var tagValue, defaultValue string - var isDefaultExists bool +type setOptions struct { + isDefaultExists bool + defaultValue string +} + +func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { + var tagValue string + var setOpt setOptions tagValue = field.Tag.Get(tag) tagValue, opts := head(tagValue, ",") @@ -132,25 +126,29 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri k, v := head(opt, "=") switch k { case "default": - isDefaultExists = true - defaultValue = v + setOpt.isDefaultExists = true + setOpt.defaultValue = v } } + return setter.TrySet(value, field, tagValue, setOpt) +} + +func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) { vs, ok := form[tagValue] - if !ok && !isDefaultExists { + if !ok && !opt.isDefaultExists { return false, nil } switch value.Kind() { case reflect.Slice: if !ok { - vs = []string{defaultValue} + vs = []string{opt.defaultValue} } return true, setSlice(vs, value, field) case reflect.Array: if !ok { - vs = []string{defaultValue} + vs = []string{opt.defaultValue} } if len(vs) != value.Len() { return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) @@ -159,7 +157,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri default: var val string if !ok { - val = defaultValue + val = opt.defaultValue } if len(vs) > 0 { From ffcbe77b1e6222b4e0e97eb1920adc1813fb2224 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sat, 6 Apr 2019 21:48:33 +0800 Subject: [PATCH 344/912] chore(readme): rollback readme (#1846) #1844 #1838 Keep the documentation in readme until full available on the new website. --- README.md | 2006 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1970 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index d3433ed..804041f 100644 --- a/README.md +++ b/README.md @@ -13,35 +13,122 @@ 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. -**The key features of Gin are:** -- Zero allocation router -- Fast -- Middleware support -- Crash-free -- JSON validation -- Routes grouping -- Error management -- Rendering built-in -- Extendable +## Contents -For more feature details, please see the [Gin website introduction](https://gin-gonic.com/docs/introduction/). +- [Installation](#installation) +- [Prerequisite](#prerequisite) +- [Quick start](#quick-start) +- [Benchmarks](#benchmarks) +- [Gin v1.stable](#gin-v1-stable) +- [Build with jsoniter](#build-with-jsoniter) +- [API Examples](#api-examples) + - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) + - [Parameters in path](#parameters-in-path) + - [Querystring parameters](#querystring-parameters) + - [Multipart/Urlencoded Form](#multiparturlencoded-form) + - [Another example: query + post form](#another-example-query--post-form) + - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) + - [Upload files](#upload-files) + - [Grouping routes](#grouping-routes) + - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) + - [Using middleware](#using-middleware) + - [How to write log file](#how-to-write-log-file) + - [Custom Log Format](#custom-log-format) + - [Model binding and validation](#model-binding-and-validation) + - [Custom Validators](#custom-validators) + - [Only Bind Query String](#only-bind-query-string) + - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Bind Uri](#bind-uri) + - [Bind HTML checkboxes](#bind-html-checkboxes) + - [Multipart/Urlencoded binding](#multiparturlencoded-binding) + - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) + - [JSONP rendering](#jsonp) + - [Serving static files](#serving-static-files) + - [Serving data from reader](#serving-data-from-reader) + - [HTML rendering](#html-rendering) + - [Multitemplate](#multitemplate) + - [Redirects](#redirects) + - [Custom Middleware](#custom-middleware) + - [Using BasicAuth() middleware](#using-basicauth-middleware) + - [Goroutines inside a middleware](#goroutines-inside-a-middleware) + - [Custom HTTP configuration](#custom-http-configuration) + - [Support Let's Encrypt](#support-lets-encrypt) + - [Run multiple service using Gin](#run-multiple-service-using-gin) + - [Graceful restart or stop](#graceful-restart-or-stop) + - [Build a single binary with templates](#build-a-single-binary-with-templates) + - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) + - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) + - [http2 server push](#http2-server-push) + - [Define format for the log of routes](#define-format-for-the-log-of-routes) + - [Set and get a cookie](#set-and-get-a-cookie) +- [Testing](#testing) +- [Users](#users) -## Getting started +## Installation -### Getting Gin +To install Gin package, you need to install Go and set your Go workspace first. -The first need [Go](https://golang.org/) installed (**version 1.6+ is required**), then you can use the below Go command to install Gin. +1. Download and install it: ```sh $ go get -u github.com/gin-gonic/gin ``` -For more installation guides such as vendor tool, please check out [Gin quickstart](https://gin-gonic.com/docs/quickstart/). +2. Import it in your code: -### Running Gin +```go +import "github.com/gin-gonic/gin" +``` + +3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. + +```go +import "net/http" +``` + +### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) + +1. `go get` govendor + +```sh +$ go get github.com/kardianos/govendor +``` +2. Create your project folder and `cd` inside + +```sh +$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" +``` + +3. Vendor init your project and add gin -First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`: +```sh +$ govendor init +$ govendor fetch github.com/gin-gonic/gin@v1.3 +``` + +4. Copy a starting template inside your project + +```sh +$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go +``` + +5. Run your project + +```sh +$ go run main.go +``` + +## Prerequisite + +Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + +## Quick start + +```sh +# assume the following codes in example.go file +$ cat example.go +``` ```go package main @@ -59,8 +146,6 @@ func main() { } ``` -And use the Go command to run the demo: - ``` # run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go @@ -68,7 +153,9 @@ $ go run example.go ## Benchmarks -Please see all benchmarks details from [Gin website](https://gin-gonic.com/docs/benchmarks/). +Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) + +[See all benchmarks](/BENCHMARKS.md) Benchmark name | (1) | (2) | (3) | (4) --------------------------------------------|-----------:|------------:|-----------:|---------: @@ -105,32 +192,1879 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better -## Middlewares +## Gin v1. stable -You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). +- [x] Zero allocation router. +- [x] Still the fastest http router and framework. From routing to writing. +- [x] Complete suite of unit tests +- [x] Battle tested +- [x] API frozen, new releases will not break your code. -## Documentation +## Build with [jsoniter](https://github.com/json-iterator/go) -See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. +Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. -All documentation is available on the Gin website. +```sh +$ go build -tags=jsoniter . +``` -- [English](https://gin-gonic.com/docs/) -- [简体中文](https://gin-gonic.com/zh-cn/docs/) -- [繁體中文](https://gin-gonic.com/zh-tw/docs/) -- [日本語](https://gin-gonic.com/ja/docs/) +## API Examples -## Examples +You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). -A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. +### Using GET, POST, PUT, PATCH, DELETE and OPTIONS -## Users +```go +func main() { + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/someGet", getting) + router.POST("/somePost", posting) + router.PUT("/somePut", putting) + router.DELETE("/someDelete", deleting) + router.PATCH("/somePatch", patching) + router.HEAD("/someHead", head) + router.OPTIONS("/someOptions", options) + + // By default it serves on :8080 unless a + // PORT environment variable was defined. + router.Run() + // router.Run(":3000") for a hard coded port +} +``` + +### Parameters in path + +```go +func main() { + router := gin.Default() + + // This handler will match /user/john but will not match /user/ or /user + router.GET("/user/:name", func(c *gin.Context) { + name := c.Param("name") + c.String(http.StatusOK, "Hello %s", name) + }) + + // However, this one will match /user/john/ and also /user/john/send + // If no other routers match /user/john, it will redirect to /user/john/ + router.GET("/user/:name/*action", func(c *gin.Context) { + name := c.Param("name") + action := c.Param("action") + message := name + " is " + action + c.String(http.StatusOK, message) + }) + + router.Run(":8080") +} +``` + +### Querystring parameters + +```go +func main() { + router := gin.Default() + + // Query string parameters are parsed using the existing underlying request object. + // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe + router.GET("/welcome", func(c *gin.Context) { + firstname := c.DefaultQuery("firstname", "Guest") + lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") + + c.String(http.StatusOK, "Hello %s %s", firstname, lastname) + }) + router.Run(":8080") +} +``` + +### Multipart/Urlencoded Form + +```go +func main() { + router := gin.Default() + + router.POST("/form_post", func(c *gin.Context) { + message := c.PostForm("message") + nick := c.DefaultPostForm("nick", "anonymous") + + c.JSON(200, gin.H{ + "status": "posted", + "message": message, + "nick": nick, + }) + }) + router.Run(":8080") +} +``` + +### Another example: query + post form + +``` +POST /post?id=1234&page=1 HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +name=manu&message=this_is_great +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + id := c.Query("id") + page := c.DefaultQuery("page", "0") + name := c.PostForm("name") + message := c.PostForm("message") + + fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) + }) + router.Run(":8080") +} +``` + +``` +id: 1234; page: 1; name: manu; message: this_is_great +``` + +### Map as querystring or postform parameters + +``` +POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +names[first]=thinkerou&names[second]=tianou +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + ids := c.QueryMap("ids") + names := c.PostFormMap("names") + + fmt.Printf("ids: %v; names: %v", ids, names) + }) + router.Run(":8080") +} +``` + +``` +ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] +``` + +### Upload files + +#### Single file + +References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single). + +`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) + +> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + // router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // single file + file, _ := c.FormFile("file") + log.Println(file.Filename) + + // Upload the file to specific dst. + // c.SaveUploadedFile(file, dst) + + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "file=@/Users/appleboy/test.zip" \ + -H "Content-Type: multipart/form-data" +``` + +#### Multiple files + +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + // router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // Multipart form + form, _ := c.MultipartForm() + files := form.File["upload[]"] -[Gin website](https://gin-gonic.com/docs/users/) lists some awesome projects made with Gin web framework. + for _, file := range files { + log.Println(file.Filename) -## Contributing + // Upload the file to specific dst. + // c.SaveUploadedFile(file, dst) + } + c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "upload[]=@/Users/appleboy/test1.zip" \ + -F "upload[]=@/Users/appleboy/test2.zip" \ + -H "Content-Type: multipart/form-data" +``` + +### Grouping routes + +```go +func main() { + router := gin.Default() + + // Simple group: v1 + v1 := router.Group("/v1") + { + v1.POST("/login", loginEndpoint) + v1.POST("/submit", submitEndpoint) + v1.POST("/read", readEndpoint) + } + + // Simple group: v2 + v2 := router.Group("/v2") + { + v2.POST("/login", loginEndpoint) + v2.POST("/submit", submitEndpoint) + v2.POST("/read", readEndpoint) + } + + router.Run(":8080") +} +``` + +### Blank Gin without middleware by default + +Use + +```go +r := gin.New() +``` + +instead of + +```go +// Default With the Logger and Recovery middleware already attached +r := gin.Default() +``` + + +### Using middleware +```go +func main() { + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.Recovery()) + + // Per route middleware, you can add as many as you desire. + r.GET("/benchmark", MyBenchLogger(), benchEndpoint) + + // Authorization group + // authorized := r.Group("/", AuthRequired()) + // exactly the same as: + authorized := r.Group("/") + // per group middleware! in this case we use the custom created + // AuthRequired() middleware just in the "authorized" group. + authorized.Use(AuthRequired()) + { + authorized.POST("/login", loginEndpoint) + authorized.POST("/submit", submitEndpoint) + authorized.POST("/read", readEndpoint) + + // nested group + testing := authorized.Group("testing") + testing.GET("/analytics", analyticsEndpoint) + } + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### How to write log file +```go +func main() { + // Disable Console Color, you don't need console color when writing the logs to file. + gin.DisableConsoleColor() + + // Logging to a file. + f, _ := os.Create("gin.log") + gin.DefaultWriter = io.MultiWriter(f) + + // Use the following code if you need to write the logs to file and console at the same time. + // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) + + router := gin.Default() + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + +    router.Run(":8080") +} +``` + +### Custom Log Format +```go +func main() { + router := gin.New() + + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + + // your custom format + return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", + param.ClientIP, + param.TimeStamp.Format(time.RFC1123), + param.Method, + param.Path, + param.Request.Proto, + param.StatusCode, + param.Latency, + param.Request.UserAgent(), + param.ErrorMessage, + ) + })) + router.Use(gin.Recovery()) + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +**Sample Output** +``` +::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " +``` + +### Controlling Log output coloring + +By default, logs output on console should be colorized depending on the detected TTY. + +Never colorize logs: + +```go +func main() { + // Disable log's color + gin.DisableConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +Always colorize logs: + +```go +func main() { + // Force log's color + gin.ForceConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +### Model binding and validation + +To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). + +Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags). + +Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. + +Also, Gin provides two sets of methods for binding: +- **Type** - Must bind + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` + - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. +- **Type** - Should bind + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML` + - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. + +When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. + +You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned. + +```go +// Binding from JSON +type Login struct { + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` +} + +func main() { + router := gin.Default() + + // Example for binding JSON ({"user": "manu", "password": "123"}) + router.POST("/loginJSON", func(c *gin.Context) { + var json Login + if err := c.ShouldBindJSON(&json); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding XML ( + // + // + // user + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding a HTML form (user=manu&password=123) + router.POST("/loginForm", func(c *gin.Context) { + var form Login + // This will infer what binder to use depending on the content-type header. + if err := c.ShouldBind(&form); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Listen and serve on 0.0.0.0:8080 + router.Run(":8080") +} +``` + +**Sample request** +```shell +$ curl -v -X POST \ + http://localhost:8080/loginJSON \ + -H 'content-type: application/json' \ + -d '{ "user": "manu" }' +> POST /loginJSON HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.51.0 +> Accept: */* +> content-type: application/json +> Content-Length: 18 +> +* upload completely sent off: 18 out of 18 bytes +< HTTP/1.1 400 Bad Request +< Content-Type: application/json; charset=utf-8 +< Date: Fri, 04 Aug 2017 03:51:31 GMT +< Content-Length: 100 +< +{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} +``` + +**Skip validate** -Gin is the work of hundreds of contributors. We appreciate your help! +When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. + +### Custom Validators + +It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). + +```go +package main + +import ( + "net/http" + "reflect" + "time" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "gopkg.in/go-playground/validator.v8" +) + +// Booking contains binded and validated data. +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 registered this way. +See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. + +### Only Bind Query String + +`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). + +```go +package main + +import ( + "log" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` +} + +func main() { + route := gin.Default() + route.Any("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + if c.ShouldBindQuery(&person) == nil { + log.Println("====== Only Bind By Query String ======") + log.Println(person.Name) + log.Println(person.Address) + } + c.String(200, "Success") +} + +``` + +### Bind Query String or Post Data + +See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). + +```go +package main + +import ( + "log" + "time" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` +} + +func main() { + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + // If `GET`, only `Form` binding engine (`query`) used. + // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + log.Println(person.Birthday) + } + + c.String(200, "Success") +} +``` + +Test it with: +```sh +$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" +``` + +### Bind Uri + +See the [detail information](https://github.com/gin-gonic/gin/issues/846). + +```go +package main + +import "github.com/gin-gonic/gin" + +type Person struct { + ID string `uri:"id" binding:"required,uuid"` + Name string `uri:"name" binding:"required"` +} + +func main() { + route := gin.Default() + route.GET("/:name/:id", func(c *gin.Context) { + var person Person + if err := c.ShouldBindUri(&person); err != nil { + c.JSON(400, gin.H{"msg": err}) + return + } + c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) + }) + route.Run(":8088") +} +``` + +Test it with: +```sh +$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 +$ curl -v localhost:8088/thinkerou/not-uuid +``` + +### Bind HTML checkboxes + +See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) + +main.go + +```go +... + +type myForm struct { + Colors []string `form:"colors[]"` +} + +... + +func formHandler(c *gin.Context) { + var fakeForm myForm + c.ShouldBind(&fakeForm) + c.JSON(200, gin.H{"color": fakeForm.Colors}) +} + +... + +``` + +form.html + +```html +
+

Check some colors

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

+ {{ .title }} +

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

+ {{ .title }} +

+

Using posts/index.tmpl

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

+ {{ .title }} +

+

Using users/index.tmpl

+ +{{ end }} +``` + +#### Custom Template renderer + +You can also use your own html template render + +```go +import "html/template" + +func main() { + router := gin.Default() + html := template.Must(template.ParseFiles("file1", "file2")) + router.SetHTMLTemplate(html) + router.Run(":8080") +} +``` + +#### Custom Delimiters + +You may use custom delims + +```go + r := gin.Default() + r.Delims("{[{", "}]}") + r.LoadHTMLGlob("/path/to/templates") +``` + +#### Custom Template Funcs + +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). + +main.go + +```go +import ( + "fmt" + "html/template" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +func formatAsDate(t time.Time) string { + year, month, day := t.Date() + return fmt.Sprintf("%d%02d/%02d", year, month, day) +} + +func main() { + router := gin.Default() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + router.LoadHTMLFiles("./testdata/template/raw.tmpl") + + router.GET("/raw", func(c *gin.Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) + + router.Run(":8080") +} + +``` + +raw.tmpl + +```html +Date: {[{.now | formatAsDate}]} +``` + +Result: +``` +Date: 2017/07/01 +``` + +### Multitemplate + +Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. + +### Redirects + +Issuing a HTTP redirect is easy. Both internal and external locations are supported. + +```go +r.GET("/test", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") +}) +``` + + +Issuing a Router redirect, use `HandleContext` like below. + +``` go +r.GET("/test", func(c *gin.Context) { + c.Request.URL.Path = "/test2" + r.HandleContext(c) +}) +r.GET("/test2", func(c *gin.Context) { + c.JSON(200, gin.H{"hello": "world"}) +}) +``` + + +### Custom Middleware + +```go +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + t := time.Now() + + // Set example variable + c.Set("example", "12345") + + // before request + + c.Next() + + // after request + latency := time.Since(t) + log.Print(latency) + + // access the status we are sending + status := c.Writer.Status() + log.Println(status) + } +} + +func main() { + r := gin.New() + r.Use(Logger()) + + r.GET("/test", func(c *gin.Context) { + example := c.MustGet("example").(string) + + // it would print: "12345" + log.Println(example) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Using BasicAuth() middleware + +```go +// simulate some private data +var secrets = gin.H{ + "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, + "austin": gin.H{"email": "austin@example.com", "phone": "666"}, + "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, +} + +func main() { + r := gin.Default() + + // Group using gin.BasicAuth() middleware + // gin.Accounts is a shortcut for map[string]string + authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ + "foo": "bar", + "austin": "1234", + "lena": "hello2", + "manu": "4321", + })) + + // /admin/secrets endpoint + // hit "localhost:8080/admin/secrets + authorized.GET("/secrets", func(c *gin.Context) { + // get user, it was set by the BasicAuth middleware + user := c.MustGet(gin.AuthUserKey).(string) + if secret, ok := secrets[user]; ok { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) + } else { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) + } + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Goroutines inside a middleware + +When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. + +```go +func main() { + r := gin.Default() + + r.GET("/long_async", func(c *gin.Context) { + // create copy to be used inside the goroutine + cCp := c.Copy() + go func() { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // note that you are using the copied context "cCp", IMPORTANT + log.Println("Done! in path " + cCp.Request.URL.Path) + }() + }) + + r.GET("/long_sync", func(c *gin.Context) { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // since we are NOT using a goroutine, we do not have to copy the context + log.Println("Done! in path " + c.Request.URL.Path) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Custom HTTP configuration + +Use `http.ListenAndServe()` directly, like this: + +```go +func main() { + router := gin.Default() + http.ListenAndServe(":8080", router) +} +``` +or + +```go +func main() { + router := gin.Default() + + s := &http.Server{ + Addr: ":8080", + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + s.ListenAndServe() +} +``` + +### Support Let's Encrypt + +example for 1-line LetsEncrypt HTTPS servers. + +```go +package main + +import ( + "log" + + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + log.Fatal(autotls.Run(r, "example1.com", "example2.com")) +} +``` + +example for custom autocert manager. + +```go +package main + +import ( + "log" + + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/acme/autocert" +) + +func main() { + r := gin.Default() + + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), + Cache: autocert.DirCache("/var/www/.cache"), + } + + log.Fatal(autotls.RunWithManager(r, &m)) +} +``` + +### Run multiple service using Gin + +See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: + +```go +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" +) + +var ( + g errgroup.Group +) + +func router01() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 01", + }, + ) + }) + + return e +} + +func router02() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 02", + }, + ) + }) + + return e +} + +func main() { + server01 := &http.Server{ + Addr: ":8080", + Handler: router01(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + server02 := &http.Server{ + Addr: ":8081", + Handler: router02(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + g.Go(func() error { + return server01.ListenAndServe() + }) + + g.Go(func() error { + return server02.ListenAndServe() + }) + + if err := g.Wait(); err != nil { + log.Fatal(err) + } +} +``` + +### Graceful restart or stop + +Do you want to graceful restart or stop your web server? +There are some ways this can be done. + +We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. + +```go +router := gin.Default() +router.GET("/", handler) +// [...] +endless.ListenAndServe(":4242", router) +``` + +An alternative to endless: + +* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. +* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. +* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. + +If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown) example with gin. + +```go +// +build go1.8 + +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + router.GET("/", func(c *gin.Context) { + time.Sleep(5 * time.Second) + c.String(http.StatusOK, "Welcome Gin Server") + }) + + srv := &http.Server{ + Addr: ":8080", + Handler: router, + } + + go func() { + // service connections + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } + }() + + // Wait for interrupt signal to gracefully shutdown the server with + // a timeout of 5 seconds. + quit := make(chan os.Signal) + // kill (no param) default send syscanll.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + log.Println("Shutdown Server ...") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + log.Fatal("Server Shutdown:", err) + } + // catching ctx.Done(). timeout of 5 seconds. + select { + case <-ctx.Done(): + log.Println("timeout of 5 seconds.") + } + 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 `https://github.com/gin-gonic/examples/tree/master/assets-in-binary` directory. + +### Bind form-data request with custom struct + +The follow example using custom struct: + +```go +type StructA struct { + FieldA string `form:"field_a"` +} + +type StructB struct { + NestedStruct StructA + FieldB string `form:"field_b"` +} + +type StructC struct { + NestedStructPointer *StructA + FieldC string `form:"field_c"` +} + +type StructD struct { + NestedAnonyStruct struct { + FieldX string `form:"field_x"` + } + FieldD string `form:"field_d"` +} + +func GetDataB(c *gin.Context) { + var b StructB + c.Bind(&b) + c.JSON(200, gin.H{ + "a": b.NestedStruct, + "b": b.FieldB, + }) +} + +func GetDataC(c *gin.Context) { + var b StructC + c.Bind(&b) + c.JSON(200, gin.H{ + "a": b.NestedStructPointer, + "c": b.FieldC, + }) +} + +func GetDataD(c *gin.Context) { + var b StructD + c.Bind(&b) + c.JSON(200, gin.H{ + "x": b.NestedAnonyStruct, + "d": b.FieldD, + }) +} + +func main() { + r := gin.Default() + r.GET("/getb", GetDataB) + r.GET("/getc", GetDataC) + r.GET("/getd", GetDataD) + + r.Run() +} +``` + +Using the command `curl` command result: + +``` +$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" +{"a":{"FieldA":"hello"},"b":"world"} +$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" +{"a":{"FieldA":"hello"},"c":"world"} +$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" +{"d":"world","x":{"FieldX":"hello"}} +``` + +### Try to bind body into different structs + +The normal methods for binding request body consumes `c.Request.Body` and they +cannot be called multiple times. + +```go +type formA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` +} + +type formB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` +} + +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This c.ShouldBind consumes c.Request.Body and it cannot be reused. + if errA := c.ShouldBind(&objA); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // Always an error is occurred by this because c.Request.Body is EOF now. + } else if errB := c.ShouldBind(&objB); errB == nil { + c.String(http.StatusOK, `the body should be formB`) + } else { + ... + } +} +``` + +For this, you can use `c.ShouldBindBodyWith`. + +```go +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This reads c.Request.Body and stores the result into the context. + if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // At this time, it reuses body stored in the context. + } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { + c.String(http.StatusOK, `the body should be formB JSON`) + // And it can accepts other formats + } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { + c.String(http.StatusOK, `the body should be formB XML`) + } else { + ... + } +} +``` + +* `c.ShouldBindBodyWith` stores body into the context before binding. This has +a slight impact to performance, so you should not use this method if you are +enough to call binding at once. +* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, +`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, +can be called by `c.ShouldBind()` multiple times without any damage to +performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). + +### http2 server push + +http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. + +```go +package main + +import ( + "html/template" + "log" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(200, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} +``` + +### Define format for the log of routes + +The default log of routes is: +``` +[GIN-debug] POST /foo --> main.main.func1 (3 handlers) +[GIN-debug] GET /bar --> main.main.func2 (3 handlers) +[GIN-debug] GET /status --> main.main.func3 (3 handlers) +``` + +If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. +In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. +```go +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) + + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) + + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + + // Listen and Server in http://0.0.0.0:8080 + r.Run() +} +``` + +### Set and get a cookie + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { + + cookie, err := c.Cookie("gin_cookie") + + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } + + fmt.Printf("Cookie value: %s \n", cookie) + }) + + router.Run() +} +``` + + +## Testing + +The `net/http/httptest` package is preferable way for HTTP testing. + +```go +package main + +func setupRouter() *gin.Engine { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + return r +} + +func main() { + r := setupRouter() + r.Run(":8080") +} +``` + +Test for code example above: + +```go +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPingRoute(t *testing.T) { + router := setupRouter() + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/ping", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "pong", w.Body.String()) +} +``` + +## Users -Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. +Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. +* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. +* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. +* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. +* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. +* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. From f9de6049cbf0820198708091e2b8e01696ec1473 Mon Sep 17 00:00:00 2001 From: Abhishek Chanda Date: Thu, 18 Apr 2019 03:45:37 +0100 Subject: [PATCH 345/912] Remove contents of the Authorization header while dumping requests (#1836) This PR replaces the contents of that header with a *. This prevents credential leak in logs. --- recovery.go | 9 ++++++++- recovery_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 9e893e1..bc946c0 100644 --- a/recovery.go +++ b/recovery.go @@ -53,11 +53,18 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { if logger != nil { stack := stack(3) httpRequest, _ := httputil.DumpRequest(c.Request, false) + headers := strings.Split(string(httpRequest), "\r\n") + for idx, header := range headers { + current := strings.Split(header, ":") + if current[0] == "Authorization" { + headers[idx] = current[0] + ": *" + } + } if brokenPipe { logger.Printf("%s\n%s%s", err, string(httpRequest), reset) } else if IsDebugging() { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", - timeFormat(time.Now()), string(httpRequest), err, stack, reset) + timeFormat(time.Now()), strings.Join(headers, "\r\n"), err, stack, reset) } else { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", timeFormat(time.Now()), err, stack, reset) diff --git a/recovery_test.go b/recovery_test.go index 0a6d627..e1a0713 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -8,6 +8,7 @@ package gin import ( "bytes" + "fmt" "net" "net/http" "os" @@ -18,6 +19,37 @@ import ( "github.com/stretchr/testify/assert" ) +func TestPanicClean(t *testing.T) { + buffer := new(bytes.Buffer) + router := New() + password := "my-super-secret-password" + router.Use(RecoveryWithWriter(buffer)) + router.GET("/recovery", func(c *Context) { + c.AbortWithStatus(http.StatusBadRequest) + panic("Oupps, Houston, we have a problem") + }) + // RUN + w := performRequest(router, "GET", "/recovery", + header{ + Key: "Host", + Value: "www.google.com", + }, + header{ + Key: "Authorization", + Value: fmt.Sprintf("Bearer %s", password), + }, + header{ + Key: "Content-Type", + Value: "application/json", + }, + ) + // TEST + assert.Equal(t, http.StatusBadRequest, w.Code) + + // Check the buffer does not have the secret key + assert.NotContains(t, buffer.String(), password) +} + // TestPanicInHandler assert that panic has been recovered. func TestPanicInHandler(t *testing.T) { buffer := new(bytes.Buffer) From 11407e73adb23e7ba4bf0fbdd02cc5336938a167 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Tue, 23 Apr 2019 01:11:57 +1000 Subject: [PATCH 346/912] Fix spelling. (#1861) --- ginS/gins.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index 3ce4a6f..3080fd3 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -118,7 +118,7 @@ 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 +// Use attaches a global middleware to the router. ie. the middlewares attached though Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { @@ -153,7 +153,7 @@ func RunUnix(file string) (err error) { // RunFd attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified file descriptor. -// Note: thie method will block the calling goroutine indefinitely unless on error happens. +// Note: the method will block the calling goroutine indefinitely unless on error happens. func RunFd(fd int) (err error) { return engine().RunFd(fd) } From 202f8fc58af47ab5c8e834662ee7fc46deacc37d Mon Sep 17 00:00:00 2001 From: DeathKing Date: Wed, 24 Apr 2019 20:21:41 +0800 Subject: [PATCH 347/912] Fix a typo syscanll.SIGTERM -> syscall.SIGTERM (#1868) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 804041f..594c8bf 100644 --- a/README.md +++ b/README.md @@ -1696,9 +1696,9 @@ func main() { // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal) - // kill (no param) default send syscanll.SIGTERM + // kill (no param) default send syscall.SIGTERM // kill -2 is syscall.SIGINT - // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it + // kill -9 is syscall.SIGKILL but can"t be catch, so don't need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutdown Server ...") From 094f9a9105f8e7b971a01a64677eef5a1f7bcd9b Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Tue, 7 May 2019 03:32:32 -0700 Subject: [PATCH 348/912] v1.4.0 + #1631 (remove go1.6/go1,7 support) (#1851) * remove go1.6 support * remove build tag * remove todo * remove go1.6 support: https://github.com/gin-gonic/gin/pull/1383/commits * update readme * remove go1.7 support * fix embedmd error * test * revert it * revert it * remove context_17 * add pusher test * v1.4.0 rc1 --- .travis.yml | 2 - CHANGELOG.md | 58 ++++++++++++++++++++++++++- README.md | 2 +- context.go | 19 ++++----- context_17.go | 17 -------- context_17_test.go | 27 ------------- context_test.go | 19 ++++++--- debug.go | 4 +- debug_test.go | 2 +- gin_integration_test.go | 37 ++++++++++++++++++ recovery_test.go | 2 - render/json.go | 18 +++++++++ render/json_17.go | 31 --------------- render/redirect.go | 4 +- render/render_17_test.go | 26 ------------- render/render_test.go | 12 ++++++ response_writer.go | 13 ++++++- response_writer_1.7.go | 12 ------ response_writer_1.8.go | 25 ------------ vendor/vendor.json | 84 +++++++++++++++++++++++++++++----------- version.go | 2 +- 21 files changed, 225 insertions(+), 191 deletions(-) delete mode 100644 context_17.go delete mode 100644 context_17_test.go delete mode 100644 render/json_17.go delete mode 100644 render/render_17_test.go delete mode 100644 response_writer_1.7.go delete mode 100644 response_writer_1.8.go diff --git a/.travis.yml b/.travis.yml index 2fd9c8a..f6ec8a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: go matrix: fast_finish: true include: - - go: 1.6.x - - go: 1.7.x - go: 1.8.x - go: 1.9.x - go: 1.10.x diff --git a/CHANGELOG.md b/CHANGELOG.md index e6a108c..8ea2495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,60 @@ -# CHANGELOG + +### Gin 1.4.0 + +- [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569) +- [NEW] Refactor of form mapping multipart requesta [#1829](https://github.com/gin-gonic/gin/pull/1829) +- [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830) +- [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802) +- [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264) +- [NEW] Add support for mapping arrays [#1797](https://github.com/gin-gonic/gin/pull/1797) +- [FIX] Readme updates [#1793](https://github.com/gin-gonic/gin/pull/1793) [#1788](https://github.com/gin-gonic/gin/pull/1788) [1789](https://github.com/gin-gonic/gin/pull/1789) +- [FIX] StaticFS: Fixed Logging two log lines on 404. [#1805](https://github.com/gin-gonic/gin/pull/1805), [#1804](https://github.com/gin-gonic/gin/pull/1804) +- [NEW] Make context.Keys available as LogFormatterParams [#1779](https://github.com/gin-gonic/gin/pull/1779) +- [NEW] Use internal/json for Marshal/Unmarshal [#1791](https://github.com/gin-gonic/gin/pull/1791) +- [NEW] Support mapping time.Duration [#1794](https://github.com/gin-gonic/gin/pull/1794) +- [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749) +- [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252) +- [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775) +- [NEW] Extend context.File to allow for the content-dispositon attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260) +- [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112) +- [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238) +- [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020) +- [NEW] Add context.HandlerNames() [#1729](https://github.com/gin-gonic/gin/pull/1729) +- [FIX] Change color methods to public in the defaultLogger. [#1771](https://github.com/gin-gonic/gin/pull/1771) +- [FIX] Update writeHeaders method to use http.Header.Set [#1722](https://github.com/gin-gonic/gin/pull/1722) +- [NEW] Add response size to LogFormatterParams [#1752](https://github.com/gin-gonic/gin/pull/1752) +- [NEW] Allow ignoring field on form mapping [#1733](https://github.com/gin-gonic/gin/pull/1733) +- [NEW] Add a function to force color in console output. [#1724](https://github.com/gin-gonic/gin/pull/1724) +- [FIX] Context.Next() - recheck len of handlers on every iteration. [#1745](https://github.com/gin-gonic/gin/pull/1745) +- [FIX] Fix all errcheck warnings [#1739](https://github.com/gin-gonic/gin/pull/1739) [#1653](https://github.com/gin-gonic/gin/pull/1653) +- [NEW] context: inherits context cancellation and deadline from http.Request context for Go>=1.7 [#1690](https://github.com/gin-gonic/gin/pull/1690) +- [NEW] Binding for URL Params [#1694](https://github.com/gin-gonic/gin/pull/1694) +- [NEW] Add LoggerWithFormatter method [#1677](https://github.com/gin-gonic/gin/pull/1677) +- [FIX] CI testing updates [#1671](https://github.com/gin-gonic/gin/pull/1671) [#1670](https://github.com/gin-gonic/gin/pull/1670) [#1682](https://github.com/gin-gonic/gin/pull/1682) [#1669](https://github.com/gin-gonic/gin/pull/1669) +- [FIX] StaticFS(): Send 404 when path does not exist [#1663](https://github.com/gin-gonic/gin/pull/1663) +- [FIX] Handle nil body for JSON binding [#1638](https://github.com/gin-gonic/gin/pull/1638) +- [FIX] Support bind uri param [#1612](https://github.com/gin-gonic/gin/pull/1612) +- [FIX] recovery: fix issue with syscall import on google app engine [#1640](https://github.com/gin-gonic/gin/pull/1640) +- [FIX] Make sure the debug log contains line breaks [#1650](https://github.com/gin-gonic/gin/pull/1650) +- [FIX] Panic stack trace being printed during recovery of broken pipe [#1089](https://github.com/gin-gonic/gin/pull/1089) [#1259](https://github.com/gin-gonic/gin/pull/1259) +- [NEW] RunFd method to run http.Server through a file descriptor [#1609](https://github.com/gin-gonic/gin/pull/1609) +- [NEW] Yaml binding support [#1618](https://github.com/gin-gonic/gin/pull/1618) +- [FIX] Pass MaxMultipartMemory when FormFile is called [#1600](https://github.com/gin-gonic/gin/pull/1600) +- [FIX] LoadHTML* tests [#1559](https://github.com/gin-gonic/gin/pull/1559) +- [FIX] Removed use of sync.pool from HandleContext [#1565](https://github.com/gin-gonic/gin/pull/1565) +- [FIX] Format output log to os.Stderr [#1571](https://github.com/gin-gonic/gin/pull/1571) +- [FIX] Make logger use a yellow background and a darkgray text for legibility [#1570](https://github.com/gin-gonic/gin/pull/1570) +- [FIX] Remove sensitive request information from panic log. [#1370](https://github.com/gin-gonic/gin/pull/1370) +- [FIX] log.Println() does not print timestamp [#829](https://github.com/gin-gonic/gin/pull/829) [#1560](https://github.com/gin-gonic/gin/pull/1560) +- [NEW] Add PureJSON renderer [#694](https://github.com/gin-gonic/gin/pull/694) +- [FIX] Add missing copyright and update if/else [#1497](https://github.com/gin-gonic/gin/pull/1497) +- [FIX] Update msgpack usage [#1498](https://github.com/gin-gonic/gin/pull/1498) +- [FIX] Use protobuf on render [#1496](https://github.com/gin-gonic/gin/pull/1496) +- [FIX] Add support for Protobuf format response [#1479](https://github.com/gin-gonic/gin/pull/1479) +- [NEW] Set default time format in form binding [#1487](https://github.com/gin-gonic/gin/pull/1487) +- [FIX] Add BindXML and ShouldBindXML [#1485](https://github.com/gin-gonic/gin/pull/1485) +- [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491) + ### Gin 1.3.0 diff --git a/README.md b/README.md index 594c8bf..3e817a7 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. Download and install it: +1. The first need [Go](https://golang.org/) installed (**version 1.8+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin diff --git a/context.go b/context.go index 5dc7f8a..af747a1 100644 --- a/context.go +++ b/context.go @@ -439,11 +439,6 @@ func (c *Context) GetPostFormArray(key string) ([]string, bool) { if values := req.PostForm[key]; len(values) > 0 { return values, true } - if req.MultipartForm != nil && req.MultipartForm.File != nil { - if values := req.MultipartForm.Value[key]; len(values) > 0 { - return values, true - } - } return []string{}, false } @@ -462,13 +457,7 @@ func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { debugPrint("error on parse multipart form map: %v", err) } } - dicts, exist := c.get(req.PostForm, key) - - if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil { - dicts, exist = c.get(req.MultipartForm.Value, key) - } - - return dicts, exist + return c.get(req.PostForm, key) } // get is an internal method and returns a map which satisfy conditions. @@ -828,6 +817,12 @@ func (c *Context) AsciiJSON(code int, obj interface{}) { c.Render(code, render.AsciiJSON{Data: obj}) } +// PureJSON serializes the given struct as JSON into the response body. +// PureJSON, unlike JSON, does not replace special html characters with their unicode entities. +func (c *Context) PureJSON(code int, obj interface{}) { + c.Render(code, render.PureJSON{Data: obj}) +} + // XML serializes the given struct as XML into the response body. // It also sets the Content-Type as "application/xml". func (c *Context) XML(code int, obj interface{}) { diff --git a/context_17.go b/context_17.go deleted file mode 100644 index 8e9f75a..0000000 --- a/context_17.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build go1.7 - -package gin - -import ( - "github.com/gin-gonic/gin/render" -) - -// PureJSON serializes the given struct as JSON into the response body. -// PureJSON, unlike JSON, does not replace special html characters with their unicode entities. -func (c *Context) PureJSON(code int, obj interface{}) { - c.Render(code, render.PureJSON{Data: obj}) -} diff --git a/context_17_test.go b/context_17_test.go deleted file mode 100644 index 5b9ebcd..0000000 --- a/context_17_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build go1.7 - -package gin - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -// Tests that the response is serialized as JSON -// and Content-Type is set to application/json -// and special HTML characters are preserved -func TestContextRenderPureJSON(t *testing.T) { - w := httptest.NewRecorder() - c, _ := CreateTestContext(w) - c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) - assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) -} diff --git a/context_test.go b/context_test.go index 0da5fbe..490e449 100644 --- a/context_test.go +++ b/context_test.go @@ -622,8 +622,7 @@ func TestContextGetCookie(t *testing.T) { } func TestContextBodyAllowedForStatus(t *testing.T) { - // todo(thinkerou): go1.6 not support StatusProcessing - assert.False(t, false, bodyAllowedForStatus(102)) + assert.False(t, false, bodyAllowedForStatus(http.StatusProcessing)) assert.False(t, false, bodyAllowedForStatus(http.StatusNoContent)) assert.False(t, false, bodyAllowedForStatus(http.StatusNotModified)) assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) @@ -794,6 +793,18 @@ func TestContextRenderNoContentAsciiJSON(t *testing.T) { assert.Equal(t, "application/json", w.Header().Get("Content-Type")) } +// Tests that the response is serialized as JSON +// and Content-Type is set to application/json +// and special HTML characters are preserved +func TestContextRenderPureJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.PureJSON(http.StatusCreated, H{"foo": "bar", "html": ""}) + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) +} + // Tests that the response executes the templates // and responds with Content-Type set to text/html func TestContextRenderHTML(t *testing.T) { @@ -1092,9 +1103,7 @@ func TestContextRenderRedirectAll(t *testing.T) { assert.Panics(t, func() { c.Redirect(299, "/resource") }) assert.Panics(t, func() { c.Redirect(309, "/resource") }) assert.NotPanics(t, func() { c.Redirect(http.StatusMultipleChoices, "/resource") }) - // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) - // when we upgrade go version we can use http.StatusPermanentRedirect - assert.NotPanics(t, func() { c.Redirect(308, "/resource") }) + assert.NotPanics(t, func() { c.Redirect(http.StatusPermanentRedirect, "/resource") }) } func TestContextNegotiationWithJSON(t *testing.T) { diff --git a/debug.go b/debug.go index 98c67cf..6d40a5d 100644 --- a/debug.go +++ b/debug.go @@ -14,7 +14,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 6 +const ginSupportMinGoVer = 8 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -69,7 +69,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + debugPrint(`[WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon. `) } diff --git a/debug_test.go b/debug_test.go index d338f0a..86a6777 100644 --- a/debug_test.go +++ b/debug_test.go @@ -91,7 +91,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/gin_integration_test.go b/gin_integration_test.go index b80cbb2..9beec14 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -8,6 +8,7 @@ import ( "bufio" "crypto/tls" "fmt" + "html/template" "io/ioutil" "net" "net/http" @@ -69,6 +70,42 @@ func TestRunTLS(t *testing.T) { testRequest(t, "https://localhost:8443/example") } +func TestPusher(t *testing.T) { + var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

+ + +`)) + + router := New() + router.Static("./assets", "./assets") + router.SetHTMLTemplate(html) + + go func() { + router.GET("/pusher", func(c *Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + pusher.Push("/assets/app.js", nil) + } + c.String(http.StatusOK, "it worked") + }) + + assert.NoError(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + }() + + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + assert.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + testRequest(t, "https://localhost:8449/pusher") +} + func TestRunEmptyWithEnv(t *testing.T) { os.Setenv("PORT", "3123") router := New() diff --git a/recovery_test.go b/recovery_test.go index e1a0713..21a0a48 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -// +build go1.7 - package gin import ( diff --git a/render/json.go b/render/json.go index c7cf330..18f27fa 100644 --- a/render/json.go +++ b/render/json.go @@ -43,6 +43,11 @@ type AsciiJSON struct { // SecureJSONPrefix is a string which represents SecureJSON prefix. type SecureJSONPrefix string +// PureJSON contains the given interface object. +type PureJSON struct { + Data interface{} +} + var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} var jsonAsciiContentType = []string{"application/json"} @@ -174,3 +179,16 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonAsciiContentType) } + +// Render (PureJSON) writes custom ContentType and encodes the given interface object. +func (r PureJSON) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + encoder := json.NewEncoder(w) + encoder.SetEscapeHTML(false) + return encoder.Encode(r.Data) +} + +// WriteContentType (PureJSON) writes custom ContentType. +func (r PureJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonContentType) +} diff --git a/render/json_17.go b/render/json_17.go deleted file mode 100644 index 208193c..0000000 --- a/render/json_17.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build go1.7 - -package render - -import ( - "net/http" - - "github.com/gin-gonic/gin/internal/json" -) - -// PureJSON contains the given interface object. -type PureJSON struct { - Data interface{} -} - -// Render (PureJSON) writes custom ContentType and encodes the given interface object. -func (r PureJSON) Render(w http.ResponseWriter) error { - r.WriteContentType(w) - encoder := json.NewEncoder(w) - encoder.SetEscapeHTML(false) - return encoder.Encode(r.Data) -} - -// WriteContentType (PureJSON) writes custom ContentType. -func (r PureJSON) WriteContentType(w http.ResponseWriter) { - writeContentType(w, jsonContentType) -} diff --git a/render/redirect.go b/render/redirect.go index 9c145fe..c006691 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -18,9 +18,7 @@ type Redirect struct { // Render (Redirect) redirects the http request to new location and writes redirect response. func (r Redirect) Render(w http.ResponseWriter) error { - // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) - // when we upgrade go version we can use http.StatusPermanentRedirect - if (r.Code < 300 || r.Code > 308) && r.Code != 201 { + if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated { panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code)) } http.Redirect(w, r.Request, r.Location, r.Code) diff --git a/render/render_17_test.go b/render/render_17_test.go deleted file mode 100644 index 6833009..0000000 --- a/render/render_17_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build go1.7 - -package render - -import ( - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRenderPureJSON(t *testing.T) { - w := httptest.NewRecorder() - data := map[string]interface{}{ - "foo": "bar", - "html": "", - } - err := (PureJSON{data}).Render(w) - assert.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) -} diff --git a/render/render_test.go b/render/render_test.go index 76e29ee..3aa5dbc 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -215,6 +215,18 @@ func TestRenderAsciiJSONFail(t *testing.T) { assert.Error(t, (AsciiJSON{data}).Render(w)) } +func TestRenderPureJSON(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + "html": "", + } + err := (PureJSON{data}).Render(w) + assert.NoError(t, err) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal diff --git a/response_writer.go b/response_writer.go index 923b53f..2682668 100644 --- a/response_writer.go +++ b/response_writer.go @@ -16,7 +16,8 @@ const ( defaultStatus = http.StatusOK ) -type responseWriterBase interface { +// ResponseWriter ... +type ResponseWriter interface { http.ResponseWriter http.Hijacker http.Flusher @@ -37,6 +38,9 @@ type responseWriterBase interface { // Forces to write the http header (status code + headers). WriteHeaderNow() + + // get the http.Pusher for server push + Pusher() http.Pusher } type responseWriter struct { @@ -113,3 +117,10 @@ func (w *responseWriter) Flush() { w.WriteHeaderNow() w.ResponseWriter.(http.Flusher).Flush() } + +func (w *responseWriter) Pusher() (pusher http.Pusher) { + if pusher, ok := w.ResponseWriter.(http.Pusher); ok { + return pusher + } + return nil +} diff --git a/response_writer_1.7.go b/response_writer_1.7.go deleted file mode 100644 index 801d196..0000000 --- a/response_writer_1.7.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build !go1.8 - -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package gin - -// ResponseWriter ... -type ResponseWriter interface { - responseWriterBase -} diff --git a/response_writer_1.8.go b/response_writer_1.8.go deleted file mode 100644 index 527c003..0000000 --- a/response_writer_1.8.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build go1.8 - -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package gin - -import ( - "net/http" -) - -// ResponseWriter ... -type ResponseWriter interface { - responseWriterBase - // get the http.Pusher for server push - Pusher() http.Pusher -} - -func (w *responseWriter) Pusher() (pusher http.Pusher) { - if pusher, ok := w.ResponseWriter.(http.Pusher); ok { - return pusher - } - return nil -} diff --git a/vendor/vendor.json b/vendor/vendor.json index 6050e8f..fc7bb11 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1,5 +1,5 @@ { - "comment": "v1.3.0", + "comment": "v1.4.0", "ignore": "test", "package": [ { @@ -13,16 +13,16 @@ { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", "path": "github.com/gin-contrib/sse", - "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", - "revisionTime": "2017-01-09T09:34:21Z" + "revision": "5545eab6dad3bbbd6c5ae9186383c2a9d23c0dae", + "revisionTime": "2019-03-01T06:25:29Z" }, { - "checksumSHA1": "mE9XW26JSpe4meBObM6J/Oeq0eg=", + "checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=", "path": "github.com/golang/protobuf/proto", - "revision": "aa810b61a9c79d51363740d207bb46cf8e620ed5", - "revisionTime": "2018-08-14T21:14:27Z", - "version": "v1.2", - "versionExact": "v1.2.0" + "revision": "c823c79ea1570fb5ff454033735a8e68575d1d0f", + "revisionTime": "2019-02-05T22:20:52Z", + "version": "v1.3", + "versionExact": "v1.3.0" }, { "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=", @@ -33,12 +33,24 @@ "versionExact": "v1.1.5" }, { - "checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=", + "checksumSHA1": "rrXDDvz+nQ2KRLQk6nxWaE5Zj1U=", "path": "github.com/mattn/go-isatty", - "revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c", - "revisionTime": "2017-11-07T05:05:31Z", + "revision": "369ecd8cea9851e459abb67eb171853e3986591e", + "revisionTime": "2019-02-25T17:38:24Z", "version": "v0.0", - "versionExact": "v0.0.4" + "versionExact": "v0.0.6" + }, + { + "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", + "path": "github.com/modern-go/concurrent", + "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", + "revisionTime": "2018-03-06T01:26:44Z" + }, + { + "checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=", + "path": "github.com/modern-go/reflect2", + "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", + "revisionTime": "2018-07-18T01:23:57Z" }, { "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", @@ -46,6 +58,20 @@ "revision": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc", "revisionTime": "2018-12-26T10:54:42Z" }, + { + "checksumSHA1": "cpNsoLqBprpKh+VZTBOZNVXzBEk=", + "path": "github.com/stretchr/objx", + "revision": "c61a9dfcced1815e7d40e214d00d1a8669a9f58c", + "revisionTime": "2019-02-11T16:23:28Z" + }, + { + "checksumSHA1": "DBdcVxnvaINHhWyyGgih/Mel6gE=", + "path": "github.com/stretchr/testify", + "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", + "revisionTime": "2018-12-05T02:12:43Z", + "version": "v1.3", + "versionExact": "v1.3.0" + }, { "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", @@ -55,12 +81,26 @@ "versionExact": "v1.2.2" }, { - "checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=", + "checksumSHA1": "fg3TzS9/QK3wZbzei3Z6O8XPLHg=", + "path": "github.com/stretchr/testify/http", + "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", + "revisionTime": "2018-12-05T02:12:43Z", + "version": "v1.3", + "versionExact": "v1.3.0" + }, + { + "checksumSHA1": "lsdl3fgOiM4Iuy7xjTQxiBtAwB0=", + "path": "github.com/stretchr/testify/mock", + "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", + "revisionTime": "2018-12-05T02:12:43Z", + "version": "v1.3", + "versionExact": "v1.3.0" + }, + { + "checksumSHA1": "WIhpR3EKGueRSJsYOZ6PIsfL4SI=", "path": "github.com/ugorji/go/codec", - "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab", - "revisionTime": "2018-04-07T10:07:33Z", - "version": "v1.1", - "versionExact": "v1.1.1" + "revision": "e444a5086c436778cf9281a7059a3d58b9e17935", + "revisionTime": "2019-02-04T20:13:41Z" }, { "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", @@ -83,13 +123,13 @@ "versionExact": "v8.18.2" }, { - "checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=", + "checksumSHA1": "QqDq2x8XOU7IoOR98Cx1eiV5QY8=", "path": "gopkg.in/yaml.v2", - "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", - "revisionTime": "2018-03-28T19:50:20Z", + "revision": "51d6538a90f86fe93ac480b35f37b2be17fef232", + "revisionTime": "2018-11-15T11:05:04Z", "version": "v2.2", - "versionExact": "v2.2.1" + "versionExact": "v2.2.2" } ], "rootPath": "github.com/gin-gonic/gin" -} +} \ No newline at end of file diff --git a/version.go b/version.go index 028caeb..07e7859 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.4.0-dev" +const Version = "v1.4.0" From 66d2c30c54ff8042f5ae13d9ebb26dfe556561fe Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Tue, 7 May 2019 14:06:55 +0300 Subject: [PATCH 349/912] binding: move tests of mapping to separate test file (#1842) * move tests of mapping to separate test file make 100% coverage of form_mapping.go from form_mapping_test.go file * fix tests for go 1.6 go 1.6 doesn't support `t.Run(...)` subtests --- binding/binding_test.go | 406 ----------------------------------- binding/form_mapping_test.go | 271 +++++++++++++++++++++++ 2 files changed, 271 insertions(+), 406 deletions(-) create mode 100644 binding/form_mapping_test.go diff --git a/binding/binding_test.go b/binding/binding_test.go index ee78822..73bb770 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -114,71 +114,6 @@ type FooStructForBoolType struct { BoolFoo bool `form:"bool_foo"` } -type FooBarStructForIntType struct { - IntFoo int `form:"int_foo"` - IntBar int `form:"int_bar" binding:"required"` -} - -type FooBarStructForInt8Type struct { - Int8Foo int8 `form:"int8_foo"` - Int8Bar int8 `form:"int8_bar" binding:"required"` -} - -type FooBarStructForInt16Type struct { - Int16Foo int16 `form:"int16_foo"` - Int16Bar int16 `form:"int16_bar" binding:"required"` -} - -type FooBarStructForInt32Type struct { - Int32Foo int32 `form:"int32_foo"` - Int32Bar int32 `form:"int32_bar" binding:"required"` -} - -type FooBarStructForInt64Type struct { - Int64Foo int64 `form:"int64_foo"` - Int64Bar int64 `form:"int64_bar" binding:"required"` -} - -type FooBarStructForUintType struct { - UintFoo uint `form:"uint_foo"` - UintBar uint `form:"uint_bar" binding:"required"` -} - -type FooBarStructForUint8Type struct { - Uint8Foo uint8 `form:"uint8_foo"` - Uint8Bar uint8 `form:"uint8_bar" binding:"required"` -} - -type FooBarStructForUint16Type struct { - Uint16Foo uint16 `form:"uint16_foo"` - Uint16Bar uint16 `form:"uint16_bar" binding:"required"` -} - -type FooBarStructForUint32Type struct { - Uint32Foo uint32 `form:"uint32_foo"` - Uint32Bar uint32 `form:"uint32_bar" binding:"required"` -} - -type FooBarStructForUint64Type struct { - Uint64Foo uint64 `form:"uint64_foo"` - Uint64Bar uint64 `form:"uint64_bar" binding:"required"` -} - -type FooBarStructForBoolType struct { - BoolFoo bool `form:"bool_foo"` - BoolBar bool `form:"bool_bar" binding:"required"` -} - -type FooBarStructForFloat32Type struct { - Float32Foo float32 `form:"float32_foo"` - Float32Bar float32 `form:"float32_bar" binding:"required"` -} - -type FooBarStructForFloat64Type struct { - Float64Foo float64 `form:"float64_foo"` - Float64Bar float64 `form:"float64_bar" binding:"required"` -} - type FooStructForStringPtrType struct { PtrFoo *string `form:"ptr_foo"` PtrBar *string `form:"ptr_bar" binding:"required"` @@ -335,110 +270,6 @@ func TestBindingFormForType(t *testing.T) { "/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2", "", "", "SliceMap") - testFormBindingForType(t, "POST", - "/", "/", - "int_foo=&int_bar=-12", "bar2=-123", "Int") - - testFormBindingForType(t, "GET", - "/?int_foo=&int_bar=-12", "/?bar2=-123", - "", "", "Int") - - testFormBindingForType(t, "POST", - "/", "/", - "int8_foo=&int8_bar=-12", "bar2=-123", "Int8") - - testFormBindingForType(t, "GET", - "/?int8_foo=&int8_bar=-12", "/?bar2=-123", - "", "", "Int8") - - testFormBindingForType(t, "POST", - "/", "/", - "int16_foo=&int16_bar=-12", "bar2=-123", "Int16") - - testFormBindingForType(t, "GET", - "/?int16_foo=&int16_bar=-12", "/?bar2=-123", - "", "", "Int16") - - testFormBindingForType(t, "POST", - "/", "/", - "int32_foo=&int32_bar=-12", "bar2=-123", "Int32") - - testFormBindingForType(t, "GET", - "/?int32_foo=&int32_bar=-12", "/?bar2=-123", - "", "", "Int32") - - testFormBindingForType(t, "POST", - "/", "/", - "int64_foo=&int64_bar=-12", "bar2=-123", "Int64") - - testFormBindingForType(t, "GET", - "/?int64_foo=&int64_bar=-12", "/?bar2=-123", - "", "", "Int64") - - testFormBindingForType(t, "POST", - "/", "/", - "uint_foo=&uint_bar=12", "bar2=123", "Uint") - - testFormBindingForType(t, "GET", - "/?uint_foo=&uint_bar=12", "/?bar2=123", - "", "", "Uint") - - testFormBindingForType(t, "POST", - "/", "/", - "uint8_foo=&uint8_bar=12", "bar2=123", "Uint8") - - testFormBindingForType(t, "GET", - "/?uint8_foo=&uint8_bar=12", "/?bar2=123", - "", "", "Uint8") - - testFormBindingForType(t, "POST", - "/", "/", - "uint16_foo=&uint16_bar=12", "bar2=123", "Uint16") - - testFormBindingForType(t, "GET", - "/?uint16_foo=&uint16_bar=12", "/?bar2=123", - "", "", "Uint16") - - testFormBindingForType(t, "POST", - "/", "/", - "uint32_foo=&uint32_bar=12", "bar2=123", "Uint32") - - testFormBindingForType(t, "GET", - "/?uint32_foo=&uint32_bar=12", "/?bar2=123", - "", "", "Uint32") - - testFormBindingForType(t, "POST", - "/", "/", - "uint64_foo=&uint64_bar=12", "bar2=123", "Uint64") - - testFormBindingForType(t, "GET", - "/?uint64_foo=&uint64_bar=12", "/?bar2=123", - "", "", "Uint64") - - testFormBindingForType(t, "POST", - "/", "/", - "bool_foo=&bool_bar=true", "bar2=true", "Bool") - - testFormBindingForType(t, "GET", - "/?bool_foo=&bool_bar=true", "/?bar2=true", - "", "", "Bool") - - testFormBindingForType(t, "POST", - "/", "/", - "float32_foo=&float32_bar=-12.34", "bar2=12.3", "Float32") - - testFormBindingForType(t, "GET", - "/?float32_foo=&float32_bar=-12.34", "/?bar2=12.3", - "", "", "Float32") - - testFormBindingForType(t, "POST", - "/", "/", - "float64_foo=&float64_bar=-12.34", "bar2=12.3", "Float64") - - testFormBindingForType(t, "GET", - "/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3", - "", "", "Float64") - testFormBindingForType(t, "POST", "/", "/", "ptr_bar=test", "bar2=test", "Ptr") @@ -1076,149 +907,6 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s req.Header.Add("Content-Type", MIMEPOSTForm) } switch typ { - case "Int": - obj := FooBarStructForIntType{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int(0), obj.IntFoo) - assert.Equal(t, int(-12), obj.IntBar) - - obj = FooBarStructForIntType{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Int8": - obj := FooBarStructForInt8Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int8(0), obj.Int8Foo) - assert.Equal(t, int8(-12), obj.Int8Bar) - - obj = FooBarStructForInt8Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Int16": - obj := FooBarStructForInt16Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int16(0), obj.Int16Foo) - assert.Equal(t, int16(-12), obj.Int16Bar) - - obj = FooBarStructForInt16Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Int32": - obj := FooBarStructForInt32Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int32(0), obj.Int32Foo) - assert.Equal(t, int32(-12), obj.Int32Bar) - - obj = FooBarStructForInt32Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Int64": - obj := FooBarStructForInt64Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int64(0), obj.Int64Foo) - assert.Equal(t, int64(-12), obj.Int64Bar) - - obj = FooBarStructForInt64Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint": - obj := FooBarStructForUintType{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint(0x0), obj.UintFoo) - assert.Equal(t, uint(0xc), obj.UintBar) - - obj = FooBarStructForUintType{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint8": - obj := FooBarStructForUint8Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint8(0x0), obj.Uint8Foo) - assert.Equal(t, uint8(0xc), obj.Uint8Bar) - - obj = FooBarStructForUint8Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint16": - obj := FooBarStructForUint16Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint16(0x0), obj.Uint16Foo) - assert.Equal(t, uint16(0xc), obj.Uint16Bar) - - obj = FooBarStructForUint16Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint32": - obj := FooBarStructForUint32Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint32(0x0), obj.Uint32Foo) - assert.Equal(t, uint32(0xc), obj.Uint32Bar) - - obj = FooBarStructForUint32Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint64": - obj := FooBarStructForUint64Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint64(0x0), obj.Uint64Foo) - assert.Equal(t, uint64(0xc), obj.Uint64Bar) - - obj = FooBarStructForUint64Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Float32": - obj := FooBarStructForFloat32Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float32(0.0), obj.Float32Foo) - assert.Equal(t, float32(-12.34), obj.Float32Bar) - - obj = FooBarStructForFloat32Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Float64": - obj := FooBarStructForFloat64Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float64(0.0), obj.Float64Foo) - assert.Equal(t, float64(-12.34), obj.Float64Bar) - - obj = FooBarStructForFloat64Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Bool": - obj := FooBarStructForBoolType{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.False(t, obj.BoolFoo) - assert.True(t, obj.BoolBar) - - obj = FooBarStructForBoolType{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) case "Slice": obj := FooStructForSliceType{} err := b.Bind(req, &obj) @@ -1454,97 +1142,3 @@ func requestWithBody(method, path, body string) (req *http.Request) { req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) return } - -func TestCanSet(t *testing.T) { - type CanSetStruct struct { - lowerStart string `form:"lower"` - } - - var c CanSetStruct - assert.Nil(t, mapForm(&c, nil)) -} - -func formPostRequest(path, body string) *http.Request { - req := requestWithBody("POST", path, body) - req.Header.Add("Content-Type", MIMEPOSTForm) - return req -} - -func TestBindingSliceDefault(t *testing.T) { - var s struct { - Friends []string `form:"friends,default=mike"` - } - req := formPostRequest("", "") - err := Form.Bind(req, &s) - assert.NoError(t, err) - - assert.Len(t, s.Friends, 1) - assert.Equal(t, "mike", s.Friends[0]) -} - -func TestBindingStructField(t *testing.T) { - var s struct { - Opts struct { - Port int - } `form:"opts"` - } - req := formPostRequest("", `opts={"Port": 8000}`) - err := Form.Bind(req, &s) - assert.NoError(t, err) - assert.Equal(t, 8000, s.Opts.Port) -} - -func TestBindingUnknownTypeChan(t *testing.T) { - var s struct { - Stop chan bool `form:"stop"` - } - req := formPostRequest("", "stop=true") - err := Form.Bind(req, &s) - assert.Error(t, err) - assert.Equal(t, errUnknownType, err) -} - -func TestBindingTimeDuration(t *testing.T) { - var s struct { - Timeout time.Duration `form:"timeout"` - } - - // ok - req := formPostRequest("", "timeout=5s") - err := Form.Bind(req, &s) - assert.NoError(t, err) - assert.Equal(t, 5*time.Second, s.Timeout) - - // error - req = formPostRequest("", "timeout=wrong") - err = Form.Bind(req, &s) - assert.Error(t, err) -} - -func TestBindingArray(t *testing.T) { - var s struct { - Nums [2]int `form:"nums,default=4"` - } - - // default - req := formPostRequest("", "") - err := Form.Bind(req, &s) - assert.Error(t, err) - assert.Equal(t, [2]int{0, 0}, s.Nums) - - // ok - req = formPostRequest("", "nums=3&nums=8") - err = Form.Bind(req, &s) - assert.NoError(t, err) - assert.Equal(t, [2]int{3, 8}, s.Nums) - - // not enough vals - req = formPostRequest("", "nums=3") - err = Form.Bind(req, &s) - assert.Error(t, err) - - // error - req = formPostRequest("", "nums=3&nums=wrong") - err = Form.Bind(req, &s) - assert.Error(t, err) -} diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go new file mode 100644 index 0000000..c9d6111 --- /dev/null +++ b/binding/form_mapping_test.go @@ -0,0 +1,271 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestMappingBaseTypes(t *testing.T) { + intPtr := func(i int) *int { + return &i + } + for _, tt := range []struct { + name string + value interface{} + form string + expect interface{} + }{ + {"base type", struct{ F int }{}, "9", int(9)}, + {"base type", struct{ F int8 }{}, "9", int8(9)}, + {"base type", struct{ F int16 }{}, "9", int16(9)}, + {"base type", struct{ F int32 }{}, "9", int32(9)}, + {"base type", struct{ F int64 }{}, "9", int64(9)}, + {"base type", struct{ F uint }{}, "9", uint(9)}, + {"base type", struct{ F uint8 }{}, "9", uint8(9)}, + {"base type", struct{ F uint16 }{}, "9", uint16(9)}, + {"base type", struct{ F uint32 }{}, "9", uint32(9)}, + {"base type", struct{ F uint64 }{}, "9", uint64(9)}, + {"base type", struct{ F bool }{}, "True", true}, + {"base type", struct{ F float32 }{}, "9.1", float32(9.1)}, + {"base type", struct{ F float64 }{}, "9.1", float64(9.1)}, + {"base type", struct{ F string }{}, "test", string("test")}, + {"base type", struct{ F *int }{}, "9", intPtr(9)}, + + // zero values + {"zero value", struct{ F int }{}, "", int(0)}, + {"zero value", struct{ F uint }{}, "", uint(0)}, + {"zero value", struct{ F bool }{}, "", false}, + {"zero value", struct{ F float32 }{}, "", float32(0)}, + } { + tp := reflect.TypeOf(tt.value) + testName := tt.name + ":" + tp.Field(0).Type.String() + + val := reflect.New(reflect.TypeOf(tt.value)) + val.Elem().Set(reflect.ValueOf(tt.value)) + + field := val.Elem().Type().Field(0) + + _, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, "form") + assert.NoError(t, err, testName) + + actual := val.Elem().Field(0).Interface() + assert.Equal(t, tt.expect, actual, testName) + } +} + +func TestMappingDefault(t *testing.T) { + var s struct { + Int int `form:",default=9"` + Slice []int `form:",default=9"` + Array [1]int `form:",default=9"` + } + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) + + assert.Equal(t, 9, s.Int) + assert.Equal(t, []int{9}, s.Slice) + assert.Equal(t, [1]int{9}, s.Array) +} + +func TestMappingSkipField(t *testing.T) { + var s struct { + A int + } + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) + + assert.Equal(t, 0, s.A) +} + +func TestMappingIgnoreField(t *testing.T) { + var s struct { + A int `form:"A"` + B int `form:"-"` + } + err := mappingByPtr(&s, formSource{"A": {"9"}, "B": {"9"}}, "form") + assert.NoError(t, err) + + assert.Equal(t, 9, s.A) + assert.Equal(t, 0, s.B) +} + +func TestMappingUnexportedField(t *testing.T) { + var s struct { + A int `form:"a"` + b int `form:"b"` + } + err := mappingByPtr(&s, formSource{"a": {"9"}, "b": {"9"}}, "form") + assert.NoError(t, err) + + assert.Equal(t, 9, s.A) + assert.Equal(t, 0, s.b) +} + +func TestMappingPrivateField(t *testing.T) { + var s struct { + f int `form:"field"` + } + err := mappingByPtr(&s, formSource{"field": {"6"}}, "form") + assert.NoError(t, err) + assert.Equal(t, int(0), s.f) +} + +func TestMappingUnknownFieldType(t *testing.T) { + var s struct { + U uintptr + } + + err := mappingByPtr(&s, formSource{"U": {"unknown"}}, "form") + assert.Error(t, err) + assert.Equal(t, errUnknownType, err) +} + +func TestMappingURI(t *testing.T) { + var s struct { + F int `uri:"field"` + } + err := mapUri(&s, map[string][]string{"field": {"6"}}) + assert.NoError(t, err) + assert.Equal(t, int(6), s.F) +} + +func TestMappingForm(t *testing.T) { + var s struct { + F int `form:"field"` + } + err := mapForm(&s, map[string][]string{"field": {"6"}}) + assert.NoError(t, err) + assert.Equal(t, int(6), s.F) +} + +func TestMappingTime(t *testing.T) { + var s struct { + Time time.Time + LocalTime time.Time `time_format:"2006-01-02"` + ZeroValue time.Time + CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` + UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` + } + + var err error + time.Local, err = time.LoadLocation("Europe/Berlin") + assert.NoError(t, err) + + err = mapForm(&s, map[string][]string{ + "Time": {"2019-01-20T16:02:58Z"}, + "LocalTime": {"2019-01-20"}, + "ZeroValue": {}, + "CSTTime": {"2019-01-20"}, + "UTCTime": {"2019-01-20"}, + }) + assert.NoError(t, err) + + assert.Equal(t, "2019-01-20 16:02:58 +0000 UTC", s.Time.String()) + assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) + assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String()) + assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String()) + assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String()) + assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String()) + assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String()) + + // wrong location + var wrongLoc struct { + Time time.Time `time_location:"wrong"` + } + err = mapForm(&wrongLoc, map[string][]string{"Time": {"2019-01-20T16:02:58Z"}}) + assert.Error(t, err) + + // wrong time value + var wrongTime struct { + Time time.Time + } + err = mapForm(&wrongTime, map[string][]string{"Time": {"wrong"}}) + assert.Error(t, err) +} + +func TestMapiingTimeDuration(t *testing.T) { + var s struct { + D time.Duration + } + + // ok + err := mappingByPtr(&s, formSource{"D": {"5s"}}, "form") + assert.NoError(t, err) + assert.Equal(t, 5*time.Second, s.D) + + // error + err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form") + assert.Error(t, err) +} + +func TestMappingSlice(t *testing.T) { + var s struct { + Slice []int `form:"slice,default=9"` + } + + // default value + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) + assert.Equal(t, []int{9}, s.Slice) + + // ok + err = mappingByPtr(&s, formSource{"slice": {"3", "4"}}, "form") + assert.NoError(t, err) + assert.Equal(t, []int{3, 4}, s.Slice) + + // error + err = mappingByPtr(&s, formSource{"slice": {"wrong"}}, "form") + assert.Error(t, err) +} + +func TestMappingArray(t *testing.T) { + var s struct { + Array [2]int `form:"array,default=9"` + } + + // wrong default + err := mappingByPtr(&s, formSource{}, "form") + assert.Error(t, err) + + // ok + err = mappingByPtr(&s, formSource{"array": {"3", "4"}}, "form") + assert.NoError(t, err) + assert.Equal(t, [2]int{3, 4}, s.Array) + + // error - not enough vals + err = mappingByPtr(&s, formSource{"array": {"3"}}, "form") + assert.Error(t, err) + + // error - wrong value + err = mappingByPtr(&s, formSource{"array": {"wrong"}}, "form") + assert.Error(t, err) +} + +func TestMappingStructField(t *testing.T) { + var s struct { + J struct { + I int + } + } + + err := mappingByPtr(&s, formSource{"J": {`{"I": 9}`}}, "form") + assert.NoError(t, err) + assert.Equal(t, 9, s.J.I) +} + +func TestMappingMapField(t *testing.T) { + var s struct { + M map[string]int + } + + err := mappingByPtr(&s, formSource{"M": {`{"one": 1}`}}, "form") + assert.NoError(t, err) + assert.Equal(t, map[string]int{"one": 1}, s.M) +} From b6425689dc657ad20762ada1591ebcc50f668c09 Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Tue, 7 May 2019 04:32:35 -0700 Subject: [PATCH 350/912] Clean the Request Path early (#1817) This will reduce the number of times we have todo a redirect. and allow multiple slashes in path to be routed! fixes #1644 --- gin.go | 1 + routes_test.go | 52 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/gin.go b/gin.go index 2d24092..4dbe983 100644 --- a/gin.go +++ b/gin.go @@ -372,6 +372,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } + rPath = cleanPath(rPath) // Find root of the tree for the given HTTP method t := engine.trees diff --git a/routes_test.go b/routes_test.go index de363a8..e16c137 100644 --- a/routes_test.go +++ b/routes_test.go @@ -22,7 +22,7 @@ type header struct { } func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder { - req, _ := http.NewRequest(method, path, nil) + req := httptest.NewRequest(method, path, nil) for _, h := range headers { req.Header.Add(h.Key, h.Value) } @@ -257,6 +257,39 @@ func TestRouteParamsByName(t *testing.T) { assert.Equal(t, "/is/super/great", wild) } +// TestContextParamsGet tests that a parameter can be parsed from the URL even with extra slashes. +func TestRouteParamsByNameWithExtraSlash(t *testing.T) { + name := "" + lastName := "" + wild := "" + router := New() + router.GET("/test/:name/:last_name/*wild", func(c *Context) { + name = c.Params.ByName("name") + lastName = c.Params.ByName("last_name") + var ok bool + wild, ok = c.Params.Get("wild") + + assert.True(t, ok) + assert.Equal(t, name, c.Param("name")) + assert.Equal(t, name, c.Param("name")) + assert.Equal(t, lastName, c.Param("last_name")) + + assert.Empty(t, c.Param("wtf")) + assert.Empty(t, c.Params.ByName("wtf")) + + wtf, ok := c.Params.Get("wtf") + assert.Empty(t, wtf) + assert.False(t, ok) + }) + + w := performRequest(router, "GET", "//test//john//smith//is//super//great") + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "john", name) + assert.Equal(t, "smith", lastName) + assert.Equal(t, "/is/super/great", wild) +} + // TestHandleStaticFile - ensure the static file handles properly func TestRouteStaticFile(t *testing.T) { // SETUP file @@ -386,15 +419,14 @@ func TestRouterNotFound(t *testing.T) { code int location string }{ - {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ - {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ - {"", http.StatusMovedPermanently, "/"}, // TSR +/ - {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case - {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case - {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ - {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ - {"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath - {"/nope", http.StatusNotFound, ""}, // NotFound + {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ + {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ + {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case + {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case + {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ + {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ + {"/../path", http.StatusOK, ""}, // CleanPath + {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) From b75d67cd51eb53c3c3a2fc406524c940021ffbda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 7 May 2019 19:43:05 +0800 Subject: [PATCH 351/912] update vendor: ugorji/go (#1879) * update vendor: ugorji/go * fix --- go.mod | 10 +++++----- go.sum | 29 ++++++++++++--------------- vendor/vendor.json | 50 +++++++++++++++++----------------------------- 3 files changed, 36 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index 0122757..1c5e995 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.12 require ( github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 - github.com/golang/protobuf v1.3.0 - github.com/json-iterator/go v1.1.5 - github.com/mattn/go-isatty v0.0.6 + github.com/golang/protobuf v1.3.1 + github.com/json-iterator/go v1.1.6 + github.com/mattn/go-isatty v0.0.7 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/stretchr/testify v1.3.0 - github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 - golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 + github.com/ugorji/go v1.1.4 + golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index 84cf837..5810468 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,12 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= -github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= -github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= -github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/mattn/go-isatty v0.0.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA= -github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= @@ -17,18 +17,15 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs= -github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA= -github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU= -golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= diff --git a/vendor/vendor.json b/vendor/vendor.json index fc7bb11..4de0bfd 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -25,20 +25,20 @@ "versionExact": "v1.3.0" }, { - "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=", + "checksumSHA1": "TB2vxux9xQbvsTHOVt4aRTuvSn4=", "path": "github.com/json-iterator/go", - "revision": "1624edc4454b8682399def8740d46db5e4362ba4", - "revisionTime": "2018-08-06T06:07:27Z", + "revision": "0ff49de124c6f76f8494e194af75bde0f1a49a29", + "revisionTime": "2019-03-06T14:29:09Z", "version": "v1.1", - "versionExact": "v1.1.5" + "versionExact": "v1.1.6" }, { - "checksumSHA1": "rrXDDvz+nQ2KRLQk6nxWaE5Zj1U=", + "checksumSHA1": "Ya+baVBU/RkXXUWD3LGFmGJiiIg=", "path": "github.com/mattn/go-isatty", - "revision": "369ecd8cea9851e459abb67eb171853e3986591e", - "revisionTime": "2019-02-25T17:38:24Z", + "revision": "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7", + "revisionTime": "2019-03-12T13:58:54Z", "version": "v0.0", - "versionExact": "v0.0.6" + "versionExact": "v0.0.7" }, { "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", @@ -81,38 +81,24 @@ "versionExact": "v1.2.2" }, { - "checksumSHA1": "fg3TzS9/QK3wZbzei3Z6O8XPLHg=", - "path": "github.com/stretchr/testify/http", - "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", - "revisionTime": "2018-12-05T02:12:43Z", - "version": "v1.3", - "versionExact": "v1.3.0" - }, - { - "checksumSHA1": "lsdl3fgOiM4Iuy7xjTQxiBtAwB0=", - "path": "github.com/stretchr/testify/mock", - "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", - "revisionTime": "2018-12-05T02:12:43Z", - "version": "v1.3", - "versionExact": "v1.3.0" - }, - { - "checksumSHA1": "WIhpR3EKGueRSJsYOZ6PIsfL4SI=", + "checksumSHA1": "csplo594qomjp2IZj82y7mTueOw=", "path": "github.com/ugorji/go/codec", - "revision": "e444a5086c436778cf9281a7059a3d58b9e17935", - "revisionTime": "2019-02-04T20:13:41Z" + "revision": "2adff0894ba3bc2eeb9f9aea45fefd49802e1a13", + "revisionTime": "2019-04-08T19:08:48Z", + "version": "v1.1", + "versionExact": "v1.1.4" }, { "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", "path": "golang.org/x/net/context", - "revision": "49bb7cea24b1df9410e1712aa6433dae904ff66a", - "revisionTime": "2018-10-11T05:27:23Z" + "revision": "f4e77d36d62c17c2336347bb2670ddbd02d092b7", + "revisionTime": "2019-05-02T22:26:14Z" }, { - "checksumSHA1": "SiJNkx+YGtq3Gtr6Ldu6OW83O+U=", + "checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=", "path": "golang.org/x/sys/unix", - "revision": "fa43e7bc11baaae89f3f902b2b4d832b68234844", - "revisionTime": "2018-10-11T14:35:51Z" + "revision": "a43fa875dd822b81eb6d2ad538bc1f4caba169bd", + "revisionTime": "2019-05-02T15:41:39Z" }, { "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", From 5a7e3095b29adc7a9caf89fe570badff28997be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 8 May 2019 11:10:34 +0800 Subject: [PATCH 352/912] Update README.md about go version (#1885) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e817a7..e980edb 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ $ go run main.go ## Prerequisite -Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. +Now Gin requires Go 1.8 or later and Go 1.10 will be required next major version. ## Quick start From 04eecb1283dbd84c3babae7b3923ac71b18a56f9 Mon Sep 17 00:00:00 2001 From: Uwe Dauernheim Date: Fri, 10 May 2019 08:03:25 +0200 Subject: [PATCH 353/912] Use DefaultWriter and DefaultErrorWriter for debug messages (#1891) Aligns behaviour according to documentation. --- debug.go | 7 ++++--- debug_test.go | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/debug.go b/debug.go index 6d40a5d..19e380f 100644 --- a/debug.go +++ b/debug.go @@ -8,7 +8,6 @@ import ( "bytes" "fmt" "html/template" - "os" "runtime" "strconv" "strings" @@ -54,7 +53,7 @@ func debugPrint(format string, values ...interface{}) { if !strings.HasSuffix(format, "\n") { format += "\n" } - fmt.Fprintf(os.Stderr, "[GIN-debug] "+format, values...) + fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...) } } @@ -98,6 +97,8 @@ at initialization. ie. before any route is registered or the router is listening func debugPrintError(err error) { if err != nil { - debugPrint("[ERROR] %v\n", err) + if IsDebugging() { + fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err) + } } } diff --git a/debug_test.go b/debug_test.go index 86a6777..9ace298 100644 --- a/debug_test.go +++ b/debug_test.go @@ -111,15 +111,15 @@ func captureOutput(t *testing.T, f func()) string { if err != nil { panic(err) } - stdout := os.Stdout - stderr := os.Stderr + defaultWriter := DefaultWriter + defaultErrorWriter := DefaultErrorWriter defer func() { - os.Stdout = stdout - os.Stderr = stderr + DefaultWriter = defaultWriter + DefaultErrorWriter = defaultErrorWriter log.SetOutput(os.Stderr) }() - os.Stdout = writer - os.Stderr = writer + DefaultWriter = writer + DefaultErrorWriter = writer log.SetOutput(writer) out := make(chan string) wg := new(sync.WaitGroup) From 965d74cebb31c1e5e0c09ec256c32d0c5db9072c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 May 2019 18:47:27 +0800 Subject: [PATCH 354/912] add dev version (#1886) * add dev version * Update version.go * Update version.go --- README.md | 4 ---- version.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index e980edb..10fb1d4 100644 --- a/README.md +++ b/README.md @@ -119,10 +119,6 @@ $ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go $ go run main.go ``` -## Prerequisite - -Now Gin requires Go 1.8 or later and Go 1.10 will be required next major version. - ## Quick start ```sh diff --git a/version.go b/version.go index 07e7859..028caeb 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.4.0" +const Version = "v1.4.0-dev" From 8ee9d959a0bcc132fae25ce61881c7effbe5c2f5 Mon Sep 17 00:00:00 2001 From: guonaihong Date: Mon, 13 May 2019 10:17:31 +0800 Subject: [PATCH 355/912] Now you can parse the inline lowercase start structure (#1893) * Now you can parse the inline lowercase start structure package main import ( "encoding/json" "fmt" "github.com/gin-gonic/gin" ) type appkey struct { Appkey string `json:"appkey" form:"appkey"` } type Query struct { Page int `json:"page" form:"page"` Size int `json:"size" form:"size"` appkey } func main() { router := gin.Default() router.POST("/login", func(c *gin.Context) { var q2 Query if c.ShouldBindQuery(&q2) == nil { c.JSON(200, &q2) } }) router.Run(":8088") } http client: old: curl -X POST "127.0.0.1:8088/login?appkey=china&page=1&size=10" {"page":1,"size":10,"appkey":""} now: curl -X POST "127.0.0.1:8088/login?appkey=china&page=1&size=10" {"page":1,"size":10,"appkey":"china"} * Modify judgment conditions --- binding/binding_test.go | 39 +++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 17 ++++++++++------- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 73bb770..6710e42 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -24,6 +24,16 @@ import ( "github.com/ugorji/go/codec" ) +type appkey struct { + Appkey string `json:"appkey" form:"appkey"` +} + +type QueryTest struct { + Page int `json:"page" form:"page"` + Size int `json:"size" form:"size"` + appkey +} + type FooStruct struct { Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"` } @@ -189,6 +199,18 @@ func TestBindingForm2(t *testing.T) { "", "") } +func TestBindingFormEmbeddedStruct(t *testing.T) { + testFormBindingEmbeddedStruct(t, "POST", + "/", "/", + "page=1&size=2&appkey=test-appkey", "bar2=foo") +} + +func TestBindingFormEmbeddedStruct2(t *testing.T) { + testFormBindingEmbeddedStruct(t, "GET", + "/?page=1&size=2&appkey=test-appkey", "/?bar2=foo", + "", "") +} + func TestBindingFormDefaultValue(t *testing.T) { testFormBindingDefaultValue(t, "POST", "/", "/", @@ -688,6 +710,23 @@ func TestUriInnerBinding(t *testing.T) { assert.Equal(t, tag.S.Age, expectedAge) } +func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, "form", b.Name()) + + obj := QueryTest{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, 1, obj.Page) + assert.Equal(t, 2, obj.Size) + assert.Equal(t, "test-appkey", obj.Appkey) + +} + func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index aaacf6c..32c5b66 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -70,12 +70,14 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag return isSetted, nil } - ok, err := tryToSetValue(value, field, setter, tag) - if err != nil { - return false, err - } - if ok { - return true, nil + if vKind != reflect.Struct || !field.Anonymous { + ok, err := tryToSetValue(value, field, setter, tag) + if err != nil { + return false, err + } + if ok { + return true, nil + } } if vKind == reflect.Struct { @@ -83,7 +85,8 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag var isSetted bool for i := 0; i < value.NumField(); i++ { - if !value.Field(i).CanSet() { + sf := tValue.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { // unexported continue } ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) From b1d607a8991147c4f3c905cd4e766eb35c83fdfa Mon Sep 17 00:00:00 2001 From: Kirill Motkov Date: Tue, 21 May 2019 18:08:52 +0300 Subject: [PATCH 356/912] Some code improvements (#1909) * strings.ToLower comparison changed to strings.EqualFold. * Rewrite switch statement with only one case as if. --- binding/form_mapping.go | 4 +--- context.go | 2 +- tree.go | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 32c5b66..ebf3b19 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -126,9 +126,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter for len(opts) > 0 { opt, opts = head(opts, ",") - k, v := head(opt, "=") - switch k { - case "default": + if k, v := head(opt, "="); k == "default" { setOpt.isDefaultExists = true setOpt.defaultValue = v } diff --git a/context.go b/context.go index af747a1..a3a7bc4 100644 --- a/context.go +++ b/context.go @@ -671,7 +671,7 @@ func (c *Context) ContentType() string { // handshake is being initiated by the client. func (c *Context) IsWebsocket() bool { if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") && - strings.ToLower(c.requestHeader("Upgrade")) == "websocket" { + strings.EqualFold(c.requestHeader("Upgrade"), "websocket") { return true } return false diff --git a/tree.go b/tree.go index ada62ce..07d6b4b 100644 --- a/tree.go +++ b/tree.go @@ -514,7 +514,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory // Outer loop for walking the tree - for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) { + for len(path) >= len(n.path) && strings.EqualFold(path[:len(n.path)], n.path) { path = path[len(n.path):] ciPath = append(ciPath, n.path...) @@ -618,7 +618,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa return ciPath, true } if len(path)+1 == len(n.path) && n.path[len(path)] == '/' && - strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) && + strings.EqualFold(path, n.path[:len(path)]) && n.handlers != nil { return append(ciPath, n.path...), true } From 0cbf290302ae06b647989effaf5e5a23028670d7 Mon Sep 17 00:00:00 2001 From: itcloudy <272685110@qq.com> Date: Wed, 22 May 2019 07:48:50 +0800 Subject: [PATCH 357/912] use encode replace json marshal increase json encoder speed (#1546) --- context_test.go | 10 +++++----- logger_test.go | 6 +++--- middleware_test.go | 2 +- render/json.go | 7 ++----- render/render_test.go | 2 +- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/context_test.go b/context_test.go index 490e449..e8dcd3d 100644 --- a/context_test.go +++ b/context_test.go @@ -660,7 +660,7 @@ func TestContextRenderJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -688,7 +688,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -714,7 +714,7 @@ func TestContextRenderAPIJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } @@ -1117,7 +1117,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { }) assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1281,7 +1281,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) { _, err := buf.ReadFrom(w.Body) assert.NoError(t, err) jsonStringBody := buf.String() - assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody) + assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}\n"), jsonStringBody) } func TestContextError(t *testing.T) { diff --git a/logger_test.go b/logger_test.go index 56bb3a0..9177e1d 100644 --- a/logger_test.go +++ b/logger_test.go @@ -369,15 +369,15 @@ func TestErrorLogger(t *testing.T) { w := performRequest(router, "GET", "/error") assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) + assert.Equal(t, "{\"error\":\"this is an error\"}\n", w.Body.String()) w = performRequest(router, "GET", "/abort") assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) + assert.Equal(t, "{\"error\":\"no authorized\"}\n", w.Body.String()) w = performRequest(router, "GET", "/print") assert.Equal(t, http.StatusInternalServerError, w.Code) - assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) + assert.Equal(t, "hola!{\"error\":\"this is an error\"}\n", w.Body.String()) } func TestLoggerWithWriterSkippingPaths(t *testing.T) { diff --git a/middleware_test.go b/middleware_test.go index fca1c53..2ae9e88 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -246,5 +246,5 @@ func TestMiddlewareWrite(t *testing.T) { w := performRequest(router, "GET", "/") assert.Equal(t, http.StatusBadRequest, w.Code) - assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) + assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}\n{\"foo\":\"bar\"}\nevent:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) } diff --git a/render/json.go b/render/json.go index 18f27fa..2b07cba 100644 --- a/render/json.go +++ b/render/json.go @@ -68,11 +68,8 @@ func (r JSON) WriteContentType(w http.ResponseWriter) { // WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) - jsonBytes, err := json.Marshal(obj) - if err != nil { - return err - } - _, err = w.Write(jsonBytes) + encoder := json.NewEncoder(w) + err := encoder.Encode(&obj) return err } diff --git a/render/render_test.go b/render/render_test.go index 3aa5dbc..9d7eaee 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -62,7 +62,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } From 78a8b5c9d58ed7a81f3e55aaf7e4b1825f2cfecd Mon Sep 17 00:00:00 2001 From: ZYunH Date: Thu, 23 May 2019 11:37:34 +0800 Subject: [PATCH 358/912] Fix typo (#1913) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 10fb1d4..2737e9a 100644 --- a/README.md +++ b/README.md @@ -1694,7 +1694,7 @@ func main() { quit := make(chan os.Signal) // kill (no param) default send syscall.SIGTERM // kill -2 is syscall.SIGINT - // kill -9 is syscall.SIGKILL but can"t be catch, so don't need add it + // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutdown Server ...") From 35e33d3638f9b5a1246dd9c72a99740f5ad4b43b Mon Sep 17 00:00:00 2001 From: Roman Zaynetdinov <627197+zaynetro@users.noreply.github.com> Date: Sun, 26 May 2019 03:20:21 +0300 Subject: [PATCH 359/912] Hold matched route full path in the Context (#1826) * Return nodeValue from getValue method * Hold route full path in the Context * Add small example --- README.md | 5 ++++ context.go | 11 ++++++++ gin.go | 14 +++++----- routes_test.go | 35 ++++++++++++++++++++++++ tree.go | 72 +++++++++++++++++++++++++++++++------------------- tree_test.go | 26 +++++++++--------- 6 files changed, 117 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 2737e9a..092e91f 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,11 @@ func main() { c.String(http.StatusOK, message) }) + // For each matched request Context will hold the route definition + router.POST("/user/:name/*action", func(c *gin.Context) { + c.FullPath() == "/user/:name/*action" // true + }) + router.Run(":8080") } ``` diff --git a/context.go b/context.go index a3a7bc4..425b627 100644 --- a/context.go +++ b/context.go @@ -48,6 +48,7 @@ type Context struct { Params Params handlers HandlersChain index int8 + fullPath string engine *Engine @@ -70,6 +71,7 @@ func (c *Context) reset() { c.Params = c.Params[0:0] c.handlers = nil c.index = -1 + c.fullPath = "" c.Keys = nil c.Errors = c.Errors[0:0] c.Accepted = nil @@ -111,6 +113,15 @@ func (c *Context) Handler() HandlerFunc { return c.handlers.Last() } +// FullPath returns a matched route full path. For not found routes +// returns an empty string. +// router.GET("/user/:id", func(c *gin.Context) { +// c.FullPath() == "/user/:id" // true +// }) +func (c *Context) FullPath() string { + return c.fullPath +} + /************************************/ /*********** FLOW CONTROL ***********/ /************************************/ diff --git a/gin.go b/gin.go index 4dbe983..220f040 100644 --- a/gin.go +++ b/gin.go @@ -252,6 +252,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { root := engine.trees.get(method) if root == nil { root = new(node) + root.fullPath = "/" engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) @@ -382,16 +383,17 @@ func (engine *Engine) handleHTTPRequest(c *Context) { } root := t[i].root // Find route in tree - handlers, params, tsr := root.getValue(rPath, c.Params, unescape) - if handlers != nil { - c.handlers = handlers - c.Params = params + value := root.getValue(rPath, c.Params, unescape) + if value.handlers != nil { + c.handlers = value.handlers + c.Params = value.params + c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && rPath != "/" { - if tsr && engine.RedirectTrailingSlash { + if value.tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return } @@ -407,7 +409,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { if tree.method == httpMethod { continue } - if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil { + if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return diff --git a/routes_test.go b/routes_test.go index e16c137..457c923 100644 --- a/routes_test.go +++ b/routes_test.go @@ -554,3 +554,38 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) { assert.Equal(t, 421, w.Code) assert.Equal(t, 0, w.Body.Len()) } + +func TestRouteContextHoldsFullPath(t *testing.T) { + router := New() + + // Test routes + routes := []string{ + "/", + "/simple", + "/project/:name", + "/project/:name/build/*params", + } + + for _, route := range routes { + actualRoute := route + router.GET(route, func(c *Context) { + // For each defined route context should contain its full path + assert.Equal(t, actualRoute, c.FullPath()) + c.AbortWithStatus(http.StatusOK) + }) + } + + for _, route := range routes { + w := performRequest(router, "GET", route) + assert.Equal(t, http.StatusOK, w.Code) + } + + // Test not found + router.Use(func(c *Context) { + // For not found routes full path is empty + assert.Equal(t, "", c.FullPath()) + }) + + w := performRequest(router, "GET", "/not-found") + assert.Equal(t, http.StatusNotFound, w.Code) +} diff --git a/tree.go b/tree.go index 07d6b4b..9a789f2 100644 --- a/tree.go +++ b/tree.go @@ -94,6 +94,7 @@ type node struct { nType nodeType maxParams uint8 wildChild bool + fullPath string } // increments priority of the given child and reorders if necessary. @@ -154,6 +155,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { children: n.children, handlers: n.handlers, priority: n.priority - 1, + fullPath: fullPath, } // Update maxParams (max of all children) @@ -229,6 +231,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { n.indices += string([]byte{c}) child := &node{ maxParams: numParams, + fullPath: fullPath, } n.children = append(n.children, child) n.incrementChildPrio(len(n.indices) - 1) @@ -296,6 +299,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle child := &node{ nType: param, maxParams: numParams, + fullPath: fullPath, } n.children = []*node{child} n.wildChild = true @@ -312,6 +316,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle child := &node{ maxParams: numParams, priority: 1, + fullPath: fullPath, } n.children = []*node{child} n = child @@ -339,6 +344,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle wildChild: true, nType: catchAll, maxParams: 1, + fullPath: fullPath, } n.children = []*node{child} n.indices = string(path[i]) @@ -352,6 +358,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle maxParams: 1, handlers: handlers, priority: 1, + fullPath: fullPath, } n.children = []*node{child} @@ -364,13 +371,21 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle n.handlers = handlers } +// nodeValue holds return values of (*Node).getValue method +type nodeValue struct { + handlers HandlersChain + params Params + tsr bool + fullPath string +} + // getValue returns the handle registered with the given path (key). The values of // wildcards are saved to a map. // If no handle can be found, a TSR (trailing slash redirect) recommendation is // made if a handle exists with an extra (without the) trailing slash for the // given path. -func (n *node) getValue(path string, po Params, unescape bool) (handlers HandlersChain, p Params, tsr bool) { - p = po +func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) { + value.params = po walk: // Outer loop for walking the tree for { if len(path) > len(n.path) { @@ -391,7 +406,7 @@ walk: // Outer loop for walking the tree // Nothing found. // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. - tsr = path == "/" && n.handlers != nil + value.tsr = path == "/" && n.handlers != nil return } @@ -406,20 +421,20 @@ walk: // Outer loop for walking the tree } // save param value - if cap(p) < int(n.maxParams) { - p = make(Params, 0, n.maxParams) + if cap(value.params) < int(n.maxParams) { + value.params = make(Params, 0, n.maxParams) } - i := len(p) - p = p[:i+1] // expand slice within preallocated capacity - p[i].Key = n.path[1:] + i := len(value.params) + value.params = value.params[:i+1] // expand slice within preallocated capacity + value.params[i].Key = n.path[1:] val := path[:end] if unescape { var err error - if p[i].Value, err = url.QueryUnescape(val); err != nil { - p[i].Value = val // fallback, in case of error + if value.params[i].Value, err = url.QueryUnescape(val); err != nil { + value.params[i].Value = val // fallback, in case of error } } else { - p[i].Value = val + value.params[i].Value = val } // we need to go deeper! @@ -431,40 +446,42 @@ walk: // Outer loop for walking the tree } // ... but we can't - tsr = len(path) == end+1 + value.tsr = len(path) == end+1 return } - if handlers = n.handlers; handlers != nil { + if value.handlers = n.handlers; value.handlers != nil { + value.fullPath = n.fullPath return } if len(n.children) == 1 { // No handle found. Check if a handle for this path + a // trailing slash exists for TSR recommendation n = n.children[0] - tsr = n.path == "/" && n.handlers != nil + value.tsr = n.path == "/" && n.handlers != nil } return case catchAll: // save param value - if cap(p) < int(n.maxParams) { - p = make(Params, 0, n.maxParams) + if cap(value.params) < int(n.maxParams) { + value.params = make(Params, 0, n.maxParams) } - i := len(p) - p = p[:i+1] // expand slice within preallocated capacity - p[i].Key = n.path[2:] + i := len(value.params) + value.params = value.params[:i+1] // expand slice within preallocated capacity + value.params[i].Key = n.path[2:] if unescape { var err error - if p[i].Value, err = url.QueryUnescape(path); err != nil { - p[i].Value = path // fallback, in case of error + if value.params[i].Value, err = url.QueryUnescape(path); err != nil { + value.params[i].Value = path // fallback, in case of error } } else { - p[i].Value = path + value.params[i].Value = path } - handlers = n.handlers + value.handlers = n.handlers + value.fullPath = n.fullPath return default: @@ -474,12 +491,13 @@ walk: // Outer loop for walking the tree } else if path == n.path { // We should have reached the node containing the handle. // Check if this node has a handle registered. - if handlers = n.handlers; handlers != nil { + if value.handlers = n.handlers; value.handlers != nil { + value.fullPath = n.fullPath return } if path == "/" && n.wildChild && n.nType != root { - tsr = true + value.tsr = true return } @@ -488,7 +506,7 @@ walk: // Outer loop for walking the tree for i := 0; i < len(n.indices); i++ { if n.indices[i] == '/' { n = n.children[i] - tsr = (len(n.path) == 1 && n.handlers != nil) || + value.tsr = (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) return } @@ -499,7 +517,7 @@ walk: // Outer loop for walking the tree // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path - tsr = (path == "/") || + value.tsr = (path == "/") || (len(n.path) == len(path)+1 && n.path[len(path)] == '/' && path == n.path[:len(n.path)-1] && n.handlers != nil) return diff --git a/tree_test.go b/tree_test.go index dbb0352..e6e2886 100644 --- a/tree_test.go +++ b/tree_test.go @@ -35,22 +35,22 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes .. } for _, request := range requests { - handler, ps, _ := tree.getValue(request.path, nil, unescape) + value := tree.getValue(request.path, nil, unescape) - if handler == nil { + if value.handlers == nil { if !request.nilHandler { t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path) } } else if request.nilHandler { t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path) } else { - handler[0](nil) + value.handlers[0](nil) if fakeHandlerValue != request.route { t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route) } } - if !reflect.DeepEqual(ps, request.ps) { + if !reflect.DeepEqual(value.params, request.ps) { t.Errorf("Params mismatch for route '%s'", request.path) } } @@ -454,10 +454,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/doc/", } for _, route := range tsrRoutes { - handler, _, tsr := tree.getValue(route, nil, false) - if handler != nil { + value := tree.getValue(route, nil, false) + if value.handlers != nil { t.Fatalf("non-nil handler for TSR route '%s", route) - } else if !tsr { + } else if !value.tsr { t.Errorf("expected TSR recommendation for route '%s'", route) } } @@ -471,10 +471,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/api/world/abc", } for _, route := range noTsrRoutes { - handler, _, tsr := tree.getValue(route, nil, false) - if handler != nil { + value := tree.getValue(route, nil, false) + if value.handlers != nil { t.Fatalf("non-nil handler for No-TSR route '%s", route) - } else if tsr { + } else if value.tsr { t.Errorf("expected no TSR recommendation for route '%s'", route) } } @@ -490,10 +490,10 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { t.Fatalf("panic inserting test route: %v", recv) } - handler, _, tsr := tree.getValue("/", nil, false) - if handler != nil { + value := tree.getValue("/", nil, false) + if value.handlers != nil { t.Fatalf("non-nil handler") - } else if tsr { + } else if value.tsr { t.Errorf("expected no TSR recommendation") } } From 6e320c97e83a61df3daa0f28695a736bece3104b Mon Sep 17 00:00:00 2001 From: Samuel Abreu Date: Mon, 27 May 2019 03:04:30 -0300 Subject: [PATCH 360/912] Fix context.Params race condition on Copy() (#1841) * Fix context.Params race condition on Copy() Using context.Param(key) on a context.Copy inside a goroutine may lead to incorrect value on a high load, where another request overwrite a Param * Using waitgroup to wait asynchronous test case --- context.go | 3 +++ context_test.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/context.go b/context.go index 425b627..3f1f8ca 100644 --- a/context.go +++ b/context.go @@ -89,6 +89,9 @@ func (c *Context) Copy() *Context { for k, v := range c.Keys { cp.Keys[k] = v } + paramCopy := make([]Param, len(cp.Params)) + copy(paramCopy, cp.Params) + cp.Params = paramCopy return &cp } diff --git a/context_test.go b/context_test.go index e8dcd3d..89cfb44 100644 --- a/context_test.go +++ b/context_test.go @@ -13,8 +13,10 @@ import ( "mime/multipart" "net/http" "net/http/httptest" + "os" "reflect" "strings" + "sync" "testing" "time" @@ -1821,3 +1823,24 @@ func TestContextResetInHandler(t *testing.T) { c.Next() }) } + +func TestRaceParamsContextCopy(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + nameGroup := router.Group("/:name") + var wg sync.WaitGroup + wg.Add(2) + { + nameGroup.GET("/api", func(c *Context) { + go func(c *Context, param string) { + defer wg.Done() + // First assert must be executed after the second request + time.Sleep(50 * time.Millisecond) + assert.Equal(t, c.Param("name"), param) + }(c.Copy(), c.Param("name")) + }) + } + performRequest(router, "GET", "/name1/api") + performRequest(router, "GET", "/name2/api") + wg.Wait() +} From 233a3e493d2adac62141371c983b55e71d805ca3 Mon Sep 17 00:00:00 2001 From: ijaa Date: Wed, 29 May 2019 11:25:02 +0800 Subject: [PATCH 361/912] add context param query cache (#1450) --- context.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 3f1f8ca..64810d5 100644 --- a/context.go +++ b/context.go @@ -60,6 +60,9 @@ type Context struct { // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string + + // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() + queryCache url.Values } /************************************/ @@ -75,6 +78,7 @@ func (c *Context) reset() { c.Keys = nil c.Errors = c.Errors[0:0] c.Accepted = nil + c.queryCache = nil } // Copy returns a copy of the current context that can be safely used outside the request's scope. @@ -385,7 +389,13 @@ func (c *Context) QueryArray(key string) []string { // GetQueryArray returns a slice of strings for a given query key, plus // a boolean value whether at least one value exists for the given key. func (c *Context) GetQueryArray(key string) ([]string, bool) { - if values, ok := c.Request.URL.Query()[key]; ok && len(values) > 0 { + + if c.queryCache == nil { + c.queryCache = make(url.Values) + c.queryCache, _ = url.ParseQuery(c.Request.URL.RawQuery) + } + + if values, ok := c.queryCache[key]; ok && len(values) > 0 { return values, true } return []string{}, false From 4b6df417e4bda80a698a64aa085779a7ad1269c0 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 29 May 2019 14:54:55 +0800 Subject: [PATCH 362/912] chore: improve GetQueryMap performance. (#1918) Signed-off-by: Bo-Yi Wu --- context.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 64810d5..71337ac 100644 --- a/context.go +++ b/context.go @@ -386,15 +386,17 @@ func (c *Context) QueryArray(key string) []string { return values } -// GetQueryArray returns a slice of strings for a given query key, plus -// a boolean value whether at least one value exists for the given key. -func (c *Context) GetQueryArray(key string) ([]string, bool) { - +func (c *Context) getQueryCache() { if c.queryCache == nil { c.queryCache = make(url.Values) c.queryCache, _ = url.ParseQuery(c.Request.URL.RawQuery) } +} +// GetQueryArray returns a slice of strings for a given query key, plus +// a boolean value whether at least one value exists for the given key. +func (c *Context) GetQueryArray(key string) ([]string, bool) { + c.getQueryCache() if values, ok := c.queryCache[key]; ok && len(values) > 0 { return values, true } @@ -410,7 +412,8 @@ func (c *Context) QueryMap(key string) map[string]string { // GetQueryMap returns a map for a given query key, plus a boolean value // whether at least one value exists for the given key. func (c *Context) GetQueryMap(key string) (map[string]string, bool) { - return c.get(c.Request.URL.Query(), key) + c.getQueryCache() + return c.get(c.queryCache, key) } // PostForm returns the specified key from a POST urlencoded form or multipart form From 08b52e5394099db4c2399357e060619c1545083e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 2 Jun 2019 17:24:41 +0800 Subject: [PATCH 363/912] feat: improve get post data. (#1920) Signed-off-by: Bo-Yi Wu --- context.go | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/context.go b/context.go index 71337ac..ffb9a2d 100644 --- a/context.go +++ b/context.go @@ -61,8 +61,12 @@ type Context struct { // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string - // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() + // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() queryCache url.Values + + // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH, + // or PUT body parameters. + formCache url.Values } /************************************/ @@ -454,16 +458,24 @@ func (c *Context) PostFormArray(key string) []string { return values } +func (c *Context) getFormCache() { + if c.formCache == nil { + c.formCache = make(url.Values) + req := c.Request + if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + if err != http.ErrNotMultipart { + debugPrint("error on parse multipart form array: %v", err) + } + } + c.formCache = req.PostForm + } +} + // GetPostFormArray returns a slice of strings for a given form key, plus // a boolean value whether at least one value exists for the given key. func (c *Context) GetPostFormArray(key string) ([]string, bool) { - req := c.Request - if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { - if err != http.ErrNotMultipart { - debugPrint("error on parse multipart form array: %v", err) - } - } - if values := req.PostForm[key]; len(values) > 0 { + c.getFormCache() + if values := c.formCache[key]; len(values) > 0 { return values, true } return []string{}, false From bfecd88fc4c22acdc93585ecc44b3a10d9702e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 3 Jun 2019 22:42:25 +0800 Subject: [PATCH 364/912] use sse v0.1.0 (#1923) --- go.mod | 2 +- go.sum | 4 ++-- vendor/vendor.json | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 1c5e995..7680d4f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.12 require ( - github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 + github.com/gin-contrib/sse v0.1.0 github.com/golang/protobuf v1.3.1 github.com/json-iterator/go v1.1.6 github.com/mattn/go-isatty v0.0.7 diff --git a/go.sum b/go.sum index 5810468..8610eae 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= diff --git a/vendor/vendor.json b/vendor/vendor.json index 4de0bfd..3e9d13b 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -11,10 +11,12 @@ "versionExact": "v1.1.1" }, { - "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", + "checksumSHA1": "qlEzrgKgIkh7y0ePm9BNo1cNdXo=", "path": "github.com/gin-contrib/sse", - "revision": "5545eab6dad3bbbd6c5ae9186383c2a9d23c0dae", - "revisionTime": "2019-03-01T06:25:29Z" + "revision": "54d8467d122d380a14768b6b4e5cd7ca4755938f", + "revisionTime": "2019-06-02T15:02:53Z", + "version": "v0.1", + "versionExact": "v0.1.0" }, { "checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=", @@ -118,4 +120,4 @@ } ], "rootPath": "github.com/gin-gonic/gin" -} \ No newline at end of file +} From 73c4633943d596bdbeaa7d02cebdd4bd0c4f4630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 3 Jun 2019 22:52:33 +0800 Subject: [PATCH 365/912] use context instead of x/net/context (#1922) --- context_test.go | 2 +- vendor/vendor.json | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/context_test.go b/context_test.go index 89cfb44..b6ecb28 100644 --- a/context_test.go +++ b/context_test.go @@ -6,6 +6,7 @@ package gin import ( "bytes" + "context" "errors" "fmt" "html/template" @@ -24,7 +25,6 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" - "golang.org/x/net/context" testdata "github.com/gin-gonic/gin/testdata/protoexample" ) diff --git a/vendor/vendor.json b/vendor/vendor.json index 3e9d13b..a225eb5 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -90,12 +90,6 @@ "version": "v1.1", "versionExact": "v1.1.4" }, - { - "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", - "path": "golang.org/x/net/context", - "revision": "f4e77d36d62c17c2336347bb2670ddbd02d092b7", - "revisionTime": "2019-05-02T22:26:14Z" - }, { "checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=", "path": "golang.org/x/sys/unix", From 75b9d2bed75a2d96198738ac3974211924dbde6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 12 Jun 2019 21:07:15 +0800 Subject: [PATCH 366/912] Attempt to fix PostForm cache bug (#1931) --- context.go | 1 + 1 file changed, 1 insertion(+) diff --git a/context.go b/context.go index ffb9a2d..77cdc18 100644 --- a/context.go +++ b/context.go @@ -83,6 +83,7 @@ func (c *Context) reset() { c.Errors = c.Errors[0:0] c.Accepted = nil c.queryCache = nil + c.formCache = nil } // Copy returns a copy of the current context that can be safely used outside the request's scope. From 09a3650c97ca7ef3a542d428a9fb2c8da8c18002 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Tue, 18 Jun 2019 14:49:10 +0300 Subject: [PATCH 367/912] binding: add support of multipart multi files (#1878) (#1949) * binding: add support of multipart multi files (#1878) * update readme: add multipart file binding --- README.md | 38 ++++--- binding/form.go | 26 ----- binding/multipart_form_mapping.go | 66 ++++++++++++ binding/multipart_form_mapping_test.go | 138 +++++++++++++++++++++++++ 4 files changed, 225 insertions(+), 43 deletions(-) create mode 100644 binding/multipart_form_mapping.go create mode 100644 binding/multipart_form_mapping_test.go diff --git a/README.md b/README.md index 092e91f..49b044a 100644 --- a/README.md +++ b/README.md @@ -959,32 +959,36 @@ result: ### Multipart/Urlencoded binding ```go -package main +type ProfileForm struct { + Name string `form:"name" binding:"required"` + Avatar *multipart.FileHeader `form:"avatar" binding:"required"` -import ( - "github.com/gin-gonic/gin" -) - -type LoginForm struct { - User string `form:"user" binding:"required"` - Password string `form:"password" binding:"required"` + // or for multiple files + // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"` } func main() { router := gin.Default() - router.POST("/login", func(c *gin.Context) { + router.POST("/profile", func(c *gin.Context) { // you can bind multipart form with explicit binding declaration: // c.ShouldBindWith(&form, binding.Form) // or you can simply use autobinding with ShouldBind method: - var form LoginForm + var form ProfileForm // in this case proper binding will be automatically selected - if c.ShouldBind(&form) == nil { - if form.User == "user" && form.Password == "password" { - c.JSON(200, gin.H{"status": "you are logged in"}) - } else { - c.JSON(401, gin.H{"status": "unauthorized"}) - } + if err := c.ShouldBind(&form); err != nil { + c.String(http.StatusBadRequest, "bad request") + return } + + err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename) + if err != nil { + c.String(http.StatusInternalServerError, "unknown error") + return + } + + // db.Save(&form) + + c.String(http.StatusOK, "ok") }) router.Run(":8080") } @@ -992,7 +996,7 @@ func main() { Test it with: ```sh -$ curl -v --form user=user --form password=password http://localhost:8080/login +$ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile ``` ### XML, JSON, YAML and ProtoBuf rendering diff --git a/binding/form.go b/binding/form.go index 0b28aa8..9e9fc3d 100644 --- a/binding/form.go +++ b/binding/form.go @@ -5,9 +5,7 @@ package binding import ( - "mime/multipart" "net/http" - "reflect" ) const defaultMemory = 32 * 1024 * 1024 @@ -63,27 +61,3 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { return validate(obj) } - -type multipartRequest http.Request - -var _ setter = (*multipartRequest)(nil) - -var ( - multipartFileHeaderStructType = reflect.TypeOf(multipart.FileHeader{}) -) - -// TrySet tries to set a value by the multipart request with the binding a form file -func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { - if value.Type() == multipartFileHeaderStructType { - _, file, err := (*http.Request)(r).FormFile(key) - if err != nil { - return false, err - } - if file != nil { - value.Set(reflect.ValueOf(*file)) - return true, nil - } - } - - return setByForm(value, field, r.MultipartForm.Value, key, opt) -} diff --git a/binding/multipart_form_mapping.go b/binding/multipart_form_mapping.go new file mode 100644 index 0000000..f85a1aa --- /dev/null +++ b/binding/multipart_form_mapping.go @@ -0,0 +1,66 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "errors" + "mime/multipart" + "net/http" + "reflect" +) + +type multipartRequest http.Request + +var _ setter = (*multipartRequest)(nil) + +// TrySet tries to set a value by the multipart request with the binding a form file +func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { + if files := r.MultipartForm.File[key]; len(files) != 0 { + return setByMultipartFormFile(value, field, files) + } + + return setByForm(value, field, r.MultipartForm.Value, key, opt) +} + +func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { + switch value.Kind() { + case reflect.Ptr: + switch value.Interface().(type) { + case *multipart.FileHeader: + value.Set(reflect.ValueOf(files[0])) + return true, nil + } + case reflect.Struct: + switch value.Interface().(type) { + case multipart.FileHeader: + value.Set(reflect.ValueOf(*files[0])) + return true, nil + } + case reflect.Slice: + slice := reflect.MakeSlice(value.Type(), len(files), len(files)) + isSetted, err = setArrayOfMultipartFormFiles(slice, field, files) + if err != nil || !isSetted { + return isSetted, err + } + value.Set(slice) + return true, nil + case reflect.Array: + return setArrayOfMultipartFormFiles(value, field, files) + } + return false, errors.New("unsupported field type for multipart.FileHeader") +} + +func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { + if value.Len() != len(files) { + return false, errors.New("unsupported len of array for []*multipart.FileHeader") + } + for i := range files { + setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1]) + if err != nil || !setted { + return setted, err + } + } + return true, nil +} diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go new file mode 100644 index 0000000..4c75d1f --- /dev/null +++ b/binding/multipart_form_mapping_test.go @@ -0,0 +1,138 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "bytes" + "io/ioutil" + "mime/multipart" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFormMultipartBindingBindOneFile(t *testing.T) { + var s struct { + FileValue multipart.FileHeader `form:"file"` + FilePtr *multipart.FileHeader `form:"file"` + SliceValues []multipart.FileHeader `form:"file"` + SlicePtrs []*multipart.FileHeader `form:"file"` + ArrayValues [1]multipart.FileHeader `form:"file"` + ArrayPtrs [1]*multipart.FileHeader `form:"file"` + } + file := testFile{"file", "file1", []byte("hello")} + + req := createRequestMultipartFiles(t, file) + err := FormMultipart.Bind(req, &s) + assert.NoError(t, err) + + assertMultipartFileHeader(t, &s.FileValue, file) + assertMultipartFileHeader(t, s.FilePtr, file) + assert.Len(t, s.SliceValues, 1) + assertMultipartFileHeader(t, &s.SliceValues[0], file) + assert.Len(t, s.SlicePtrs, 1) + assertMultipartFileHeader(t, s.SlicePtrs[0], file) + assertMultipartFileHeader(t, &s.ArrayValues[0], file) + assertMultipartFileHeader(t, s.ArrayPtrs[0], file) +} + +func TestFormMultipartBindingBindTwoFiles(t *testing.T) { + var s struct { + SliceValues []multipart.FileHeader `form:"file"` + SlicePtrs []*multipart.FileHeader `form:"file"` + ArrayValues [2]multipart.FileHeader `form:"file"` + ArrayPtrs [2]*multipart.FileHeader `form:"file"` + } + files := []testFile{ + {"file", "file1", []byte("hello")}, + {"file", "file2", []byte("world")}, + } + + req := createRequestMultipartFiles(t, files...) + err := FormMultipart.Bind(req, &s) + assert.NoError(t, err) + + assert.Len(t, s.SliceValues, len(files)) + assert.Len(t, s.SlicePtrs, len(files)) + assert.Len(t, s.ArrayValues, len(files)) + assert.Len(t, s.ArrayPtrs, len(files)) + + for i, file := range files { + assertMultipartFileHeader(t, &s.SliceValues[i], file) + assertMultipartFileHeader(t, s.SlicePtrs[i], file) + assertMultipartFileHeader(t, &s.ArrayValues[i], file) + assertMultipartFileHeader(t, s.ArrayPtrs[i], file) + } +} + +func TestFormMultipartBindingBindError(t *testing.T) { + files := []testFile{ + {"file", "file1", []byte("hello")}, + {"file", "file2", []byte("world")}, + } + + for _, tt := range []struct { + name string + s interface{} + }{ + {"wrong type", &struct { + Files int `form:"file"` + }{}}, + {"wrong array size", &struct { + Files [1]*multipart.FileHeader `form:"file"` + }{}}, + {"wrong slice type", &struct { + Files []int `form:"file"` + }{}}, + } { + req := createRequestMultipartFiles(t, files...) + err := FormMultipart.Bind(req, tt.s) + assert.Error(t, err) + } +} + +type testFile struct { + Fieldname string + Filename string + Content []byte +} + +func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request { + var body bytes.Buffer + + mw := multipart.NewWriter(&body) + for _, file := range files { + fw, err := mw.CreateFormFile(file.Fieldname, file.Filename) + assert.NoError(t, err) + + n, err := fw.Write(file.Content) + assert.NoError(t, err) + assert.Equal(t, len(file.Content), n) + } + err := mw.Close() + assert.NoError(t, err) + + req, err := http.NewRequest("POST", "/", &body) + assert.NoError(t, err) + + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary()) + return req +} + +func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) { + assert.Equal(t, file.Filename, fh.Filename) + // assert.Equal(t, int64(len(file.Content)), fh.Size) // fh.Size does not exist on go1.8 + + fl, err := fh.Open() + assert.NoError(t, err) + + body, err := ioutil.ReadAll(fl) + assert.NoError(t, err) + assert.Equal(t, string(file.Content), string(body)) + + err = fl.Close() + assert.NoError(t, err) +} From f98b339b773105aad77f321d0baaa30475bf875d Mon Sep 17 00:00:00 2001 From: guonaihong Date: Thu, 27 Jun 2019 12:47:45 +0800 Subject: [PATCH 368/912] support bind http header param #1956 (#1957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * support bind http header param #1956 update #1956 ``` package main import ( "fmt" "github.com/gin-gonic/gin" ) type testHeader struct { Rate int `header:"Rate"` Domain string `header:"Domain"` } func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { h := testHeader{} if err := c.ShouldBindHeader(&h); err != nil { c.JSON(200, err) } fmt.Printf("%#v\n", h) c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain}) }) r.Run() // client // curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ // output // {"Domain":"music","Rate":300} } ``` * add unit test * Modify the code to get the http header When the http header is obtained in the standard library, the key value will be modified by the CanonicalMIMEHeaderKey function, and finally the value of the http header will be obtained from the map. As follows. ```go func (h MIMEHeader) Get(key string) string {         // ...          v := h[CanonicalMIMEHeaderKey(key)]         // ... } ``` This pr also follows this modification * Thanks to vkd for suggestions, modifying code * Increase test coverage env GOPATH=`pwd` go test github.com/gin-gonic/gin/binding -coverprofile=cover.prof ok github.com/gin-gonic/gin/binding 0.015s coverage: 100.0% of statements * Rollback check code * add use case to README.md --- README.md | 38 +++++++++++++++++++++++++++++++++++ binding/binding.go | 1 + binding/binding_test.go | 25 +++++++++++++++++++++++ binding/header.go | 34 +++++++++++++++++++++++++++++++ context.go | 10 ++++++++++ context_test.go | 44 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+) create mode 100644 binding/header.go diff --git a/README.md b/README.md index 49b044a..aa5043a 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind Uri](#bind-uri) + - [Bind Header](#bind-header) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) @@ -910,6 +911,43 @@ $ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 $ curl -v localhost:8088/thinkerou/not-uuid ``` +### Bind Header + +```go +package main + +import ( + "fmt" + "github.com/gin-gonic/gin" +) + +type testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` +} + +func main() { + r := gin.Default() + r.GET("/", func(c *gin.Context) { + h := testHeader{} + + if err := c.ShouldBindHeader(&h); err != nil { + c.JSON(200, err) + } + + fmt.Printf("%#v\n", h) + c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain}) + }) + + r.Run() + +// client +// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ +// output +// {"Domain":"music","Rate":300} +} +``` + ### Bind HTML checkboxes See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) diff --git a/binding/binding.go b/binding/binding.go index 520c510..6d58c3c 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -78,6 +78,7 @@ var ( MsgPack = msgpackBinding{} YAML = yamlBinding{} Uri = uriBinding{} + Header = headerBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method diff --git a/binding/binding_test.go b/binding/binding_test.go index 6710e42..827518f 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -667,6 +667,31 @@ func TestExistsFails(t *testing.T) { assert.Error(t, err) } +func TestHeaderBinding(t *testing.T) { + h := Header + assert.Equal(t, "header", h.Name()) + + type tHeader struct { + Limit int `header:"limit"` + } + + var theader tHeader + req := requestWithBody("GET", "/", "") + req.Header.Add("limit", "1000") + assert.NoError(t, h.Bind(req, &theader)) + assert.Equal(t, 1000, theader.Limit) + + req = requestWithBody("GET", "/", "") + req.Header.Add("fail", `{fail:fail}`) + + type failStruct struct { + Fail map[string]interface{} `header:"fail"` + } + + err := h.Bind(req, &failStruct{}) + assert.Error(t, err) +} + func TestUriBinding(t *testing.T) { b := Uri assert.Equal(t, "uri", b.Name()) diff --git a/binding/header.go b/binding/header.go new file mode 100644 index 0000000..179ce4e --- /dev/null +++ b/binding/header.go @@ -0,0 +1,34 @@ +package binding + +import ( + "net/http" + "net/textproto" + "reflect" +) + +type headerBinding struct{} + +func (headerBinding) Name() string { + return "header" +} + +func (headerBinding) Bind(req *http.Request, obj interface{}) error { + + if err := mapHeader(obj, req.Header); err != nil { + return err + } + + return validate(obj) +} + +func mapHeader(ptr interface{}, h map[string][]string) error { + return mappingByPtr(ptr, headerSource(h), "header") +} + +type headerSource map[string][]string + +var _ setter = headerSource(nil) + +func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { + return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt) +} diff --git a/context.go b/context.go index 77cdc18..d9fcc28 100644 --- a/context.go +++ b/context.go @@ -583,6 +583,11 @@ func (c *Context) BindYAML(obj interface{}) error { return c.MustBindWith(obj, binding.YAML) } +// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). +func (c *Context) BindHeader(obj interface{}) error { + return c.MustBindWith(obj, binding.Header) +} + // BindUri binds the passed struct pointer using binding.Uri. // It will abort the request with HTTP 400 if any error occurs. func (c *Context) BindUri(obj interface{}) error { @@ -637,6 +642,11 @@ func (c *Context) ShouldBindYAML(obj interface{}) error { return c.ShouldBindWith(obj, binding.YAML) } +// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). +func (c *Context) ShouldBindHeader(obj interface{}) error { + return c.ShouldBindWith(obj, binding.Header) +} + // ShouldBindUri binds the passed struct pointer using the specified binding engine. func (c *Context) ShouldBindUri(obj interface{}) error { m := make(map[string][]string) diff --git a/context_test.go b/context_test.go index b6ecb28..439e8ee 100644 --- a/context_test.go +++ b/context_test.go @@ -1436,6 +1436,28 @@ func TestContextBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextBindHeader(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("rate", "8000") + c.Request.Header.Add("domain", "music") + c.Request.Header.Add("limit", "1000") + + var testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` + Limit int `header:"limit"` + } + + assert.NoError(t, c.BindHeader(&testHeader)) + assert.Equal(t, 8000, testHeader.Rate) + assert.Equal(t, "music", testHeader.Domain) + assert.Equal(t, 1000, testHeader.Limit) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -1543,6 +1565,28 @@ func TestContextShouldBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextShouldBindHeader(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("rate", "8000") + c.Request.Header.Add("domain", "music") + c.Request.Header.Add("limit", "1000") + + var testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` + Limit int `header:"limit"` + } + + assert.NoError(t, c.ShouldBindHeader(&testHeader)) + assert.Equal(t, 8000, testHeader.Rate) + assert.Equal(t, "music", testHeader.Domain) + assert.Equal(t, 1000, testHeader.Limit) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From 31342fc03febd3fb35a269191a40d01311ba87a6 Mon Sep 17 00:00:00 2001 From: guonaihong Date: Fri, 28 Jun 2019 09:25:19 +0800 Subject: [PATCH 369/912] fix README.md code bug and Change map to gin.H (#1963) ``` go func main() { r := gin.Default() // r.GET("/JSONP?callback=x", func(c *gin.Context) { // old r.GET("/JSONP", func(c *gin.Context) { // new data := gin.H{ "foo": "bar", } //callback is x // Will output : x({\"foo\":\"bar\"}) c.JSONP(http.StatusOK, data) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } // client // curl http://127.0.0.1:8080/JSONP?callback=x // old output // 404 page not found // new output // x({"foo":"bar"}) ``` Most of the sample code in the documentation map[string]interface{} is represented by gin.H. gin.H is a very important place for me to like gin, can write a lot less code --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aa5043a..aa546e5 100644 --- a/README.md +++ b/README.md @@ -1119,8 +1119,8 @@ Using JSONP to request data from a server in a different domain. Add callback t func main() { r := gin.Default() - r.GET("/JSONP?callback=x", func(c *gin.Context) { - data := map[string]interface{}{ + r.GET("/JSONP", func(c *gin.Context) { + data := gin.H{ "foo": "bar", } @@ -1131,6 +1131,9 @@ func main() { // Listen and serve on 0.0.0.0:8080 r.Run(":8080") + + // client + // curl http://127.0.0.1:8080/JSONP?callback=x } ``` @@ -1143,7 +1146,7 @@ func main() { r := gin.Default() r.GET("/someJSON", func(c *gin.Context) { - data := map[string]interface{}{ + data := gin.H{ "lang": "GO语言", "tag": "
", } @@ -1352,7 +1355,7 @@ func main() { router.LoadHTMLFiles("./testdata/template/raw.tmpl") router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + c.HTML(http.StatusOK, "raw.tmpl", gin.H{ "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) From 46acb91996921079112470de9068cd4cea503a70 Mon Sep 17 00:00:00 2001 From: srt180 <30768686+srt180@users.noreply.github.com> Date: Fri, 28 Jun 2019 09:34:14 +0800 Subject: [PATCH 370/912] modify readme example code (#1961) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa546e5..f79c0c1 100644 --- a/README.md +++ b/README.md @@ -756,7 +756,7 @@ func bookableDate( ) bool { if date, ok := field.Interface().(time.Time); ok { today := time.Now() - if today.Year() > date.Year() || today.YearDay() > date.YearDay() { + if today.After(date) { return false } } From fc920dc56141d11599201b2b35838cc9449d5a2a Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Fri, 28 Jun 2019 08:43:07 -0700 Subject: [PATCH 371/912] Drop Support for go1.8 and go1.9 (#1933) --- .travis.yml | 2 -- README.md | 2 +- debug.go | 4 ++-- debug_test.go | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f6ec8a8..27c80ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: go matrix: fast_finish: true include: - - go: 1.8.x - - go: 1.9.x - go: 1.10.x - go: 1.11.x env: GO111MODULE=on diff --git a/README.md b/README.md index f79c0c1..4257df3 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. The first need [Go](https://golang.org/) installed (**version 1.8+ is required**), then you can use the below Go command to install Gin. +1. The first need [Go](https://golang.org/) installed (**version 1.10+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin diff --git a/debug.go b/debug.go index 19e380f..64777c2 100644 --- a/debug.go +++ b/debug.go @@ -13,7 +13,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 8 +const ginSupportMinGoVer = 10 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -68,7 +68,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon. + debugPrint(`[WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon. `) } diff --git a/debug_test.go b/debug_test.go index 9ace298..d6f320e 100644 --- a/debug_test.go +++ b/debug_test.go @@ -91,7 +91,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } From f65018d7b1f1a5d54e5791e9e9ac76e9946eae36 Mon Sep 17 00:00:00 2001 From: bbiao Date: Fri, 28 Jun 2019 23:54:52 +0800 Subject: [PATCH 372/912] Bugfix for the FullPath feature (#1919) * worked with more complex situations * the original pr not work when and a short route with the same prefix to some already added routes --- routes_test.go | 7 ++++++- tree.go | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/routes_test.go b/routes_test.go index 457c923..0c2f9a0 100644 --- a/routes_test.go +++ b/routes_test.go @@ -560,10 +560,15 @@ func TestRouteContextHoldsFullPath(t *testing.T) { // Test routes routes := []string{ - "/", "/simple", "/project/:name", + "/", + "/news/home", + "/news", + "/simple-two/one", + "/simple-two/one-two", "/project/:name/build/*params", + "/project/:name/bui", } for _, route := range routes { diff --git a/tree.go b/tree.go index 9a789f2..371d5ad 100644 --- a/tree.go +++ b/tree.go @@ -128,6 +128,8 @@ func (n *node) addRoute(path string, handlers HandlersChain) { n.priority++ numParams := countParams(path) + parentFullPathIndex := 0 + // non-empty tree if len(n.path) > 0 || len(n.children) > 0 { walk: @@ -155,7 +157,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { children: n.children, handlers: n.handlers, priority: n.priority - 1, - fullPath: fullPath, + fullPath: n.fullPath, } // Update maxParams (max of all children) @@ -171,6 +173,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { n.path = path[:i] n.handlers = nil n.wildChild = false + n.fullPath = fullPath[:parentFullPathIndex+i] } // Make new node a child of this node @@ -178,6 +181,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { path = path[i:] if n.wildChild { + parentFullPathIndex += len(n.path) n = n.children[0] n.priority++ @@ -211,6 +215,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { // slash after param if n.nType == param && c == '/' && len(n.children) == 1 { + parentFullPathIndex += len(n.path) n = n.children[0] n.priority++ continue walk @@ -219,6 +224,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { // Check if a child with the next path byte exists for i := 0; i < len(n.indices); i++ { if c == n.indices[i] { + parentFullPathIndex += len(n.path) i = n.incrementChildPrio(i) n = n.children[i] continue walk @@ -369,6 +375,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle // insert remaining path part and handle to the leaf n.path = path[offset:] n.handlers = handlers + n.fullPath = fullPath } // nodeValue holds return values of (*Node).getValue method From 3f53a58d4ad33bf881c028936b0b44f2c3210d56 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sat, 29 Jun 2019 00:09:53 +0800 Subject: [PATCH 373/912] Add user case: brigade (#1937) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4257df3..8d7705d 100644 --- a/README.md +++ b/README.md @@ -2114,3 +2114,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. * [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. +* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. From b67bc8f00502b1f555b9db64cd2dfc03098cfc8f Mon Sep 17 00:00:00 2001 From: guonaihong Date: Sat, 29 Jun 2019 20:43:32 +0800 Subject: [PATCH 374/912] Gin1.5 bytes.Buffer to strings.Builder (#1939) * Replace bytes.Buffer to strings.Builder * Merge the latest changes * Update errors.go --- debug.go | 3 +-- errors.go | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/debug.go b/debug.go index 64777c2..49080db 100644 --- a/debug.go +++ b/debug.go @@ -5,7 +5,6 @@ package gin import ( - "bytes" "fmt" "html/template" "runtime" @@ -38,7 +37,7 @@ func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { func debugPrintLoadTemplate(tmpl *template.Template) { if IsDebugging() { - var buf bytes.Buffer + var buf strings.Builder for _, tmpl := range tmpl.Templates() { buf.WriteString("\t- ") buf.WriteString(tmpl.Name()) diff --git a/errors.go b/errors.go index 6070ff5..25e8ff6 100644 --- a/errors.go +++ b/errors.go @@ -5,9 +5,9 @@ package gin import ( - "bytes" "fmt" "reflect" + "strings" "github.com/gin-gonic/gin/internal/json" ) @@ -158,7 +158,7 @@ func (a errorMsgs) String() string { if len(a) == 0 { return "" } - var buffer bytes.Buffer + var buffer strings.Builder for i, msg := range a { fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err) if msg.Meta != nil { From 6f7276fdc1d3cfad2052ba770382afd2f4d41dbf Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Sun, 30 Jun 2019 08:55:09 +0800 Subject: [PATCH 375/912] Update CHANGELOG.md (#1966) typo fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ea2495..15dfb1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Gin 1.4.0 - [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569) -- [NEW] Refactor of form mapping multipart requesta [#1829](https://github.com/gin-gonic/gin/pull/1829) +- [NEW] Refactor of form mapping multipart request [#1829](https://github.com/gin-gonic/gin/pull/1829) - [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830) - [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802) - [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264) From e602d524cccad90261e10bbb5ca41e9a81e467d4 Mon Sep 17 00:00:00 2001 From: Rafal Zajac Date: Thu, 4 Jul 2019 01:57:52 +0200 Subject: [PATCH 376/912] Typo (#1971) --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index f4532d5..71b80de 100644 --- a/utils.go +++ b/utils.go @@ -146,6 +146,6 @@ func resolveAddress(addr []string) string { case 1: return addr[0] default: - panic("too much parameters") + panic("too many parameters") } } From 0349de518b3bd862f664d1e58565a3d3bff6a771 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 10 Jul 2019 06:20:20 +0800 Subject: [PATCH 377/912] upgrade github.com/ugorji/go/codec (#1969) --- go.mod | 5 ++--- go.sum | 15 ++++++--------- vendor/vendor.json | 8 ++++---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 7680d4f..dbe8afc 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,11 @@ require ( github.com/gin-contrib/sse v0.1.0 github.com/golang/protobuf v1.3.1 github.com/json-iterator/go v1.1.6 - github.com/mattn/go-isatty v0.0.7 + github.com/mattn/go-isatty v0.0.8 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/stretchr/testify v1.3.0 - github.com/ugorji/go v1.1.4 - golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c + github.com/ugorji/go/codec v1.1.7 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index 8610eae..c1e9f22 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= @@ -17,15 +17,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= diff --git a/vendor/vendor.json b/vendor/vendor.json index a225eb5..fa8fd13 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -83,12 +83,12 @@ "versionExact": "v1.2.2" }, { - "checksumSHA1": "csplo594qomjp2IZj82y7mTueOw=", + "checksumSHA1": "S4ei9eSqVThDio0Jn2sav6yUbvg=", "path": "github.com/ugorji/go/codec", - "revision": "2adff0894ba3bc2eeb9f9aea45fefd49802e1a13", - "revisionTime": "2019-04-08T19:08:48Z", + "revision": "82dbfaf494e3b01d2d481376f11f6a5c8cf9599f", + "revisionTime": "2019-07-02T14:15:27Z", "version": "v1.1", - "versionExact": "v1.1.4" + "versionExact": "v1.1.6" }, { "checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=", From 502c898d755b2156b295ea8bdbbecf9ba374d067 Mon Sep 17 00:00:00 2001 From: guonaihong Date: Wed, 10 Jul 2019 13:02:40 +0800 Subject: [PATCH 378/912] binding: support unix time (#1980) * binding: support unix time ref:#1979 * binding: support unix time add test file modify readme ```golang package main import ( "fmt" "github.com/gin-gonic/gin" "time" ) type shareTime struct { CreateTime time.Time `form:"createTime" time_format:"unixNano"` UnixTime time.Time `form:"unixTime" time_format:"unix"` } func main() { r := gin.Default() unix := r.Group("/unix") testCT := time.Date(2019, 7, 6, 16, 0, 33, 123, time.Local) fmt.Printf("%d\n", testCT.UnixNano()) testUT := time.Date(2019, 7, 6, 16, 0, 33, 0, time.Local) fmt.Printf("%d\n", testUT.Unix()) unix.GET("/nano", func(c *gin.Context) { s := shareTime{} c.ShouldBindQuery(&s) if !testCT.Equal(s.CreateTime) { c.String(500, "want %d got %d", testCT.UnixNano(), s.CreateTime) return } c.JSON(200, s) }) unix.GET("/sec", func(c *gin.Context) { s := shareTime{} c.ShouldBindQuery(&s) if !testUT.Equal(s.UnixTime) { c.String(500, "want %d got %d", testCT.Unix(), s.UnixTime) return } c.JSON(200, s) }) r.Run() } ``` * Contraction variable scope --- README.md | 22 +++++++++++++--------- binding/binding_test.go | 41 +++++++++++++++++++++++++++++++++++++---- binding/form_mapping.go | 18 ++++++++++++++++++ 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8d7705d..2761259 100644 --- a/README.md +++ b/README.md @@ -846,9 +846,11 @@ import ( ) type Person struct { - Name string `form:"name"` - Address string `form:"address"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` } func main() { @@ -862,11 +864,13 @@ 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.ShouldBind(&person) == nil { - log.Println(person.Name) - log.Println(person.Address) - log.Println(person.Birthday) - } + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + log.Println(person.Birthday) + log.Println(person.CreateTime) + log.Println(person.UnixTime) + } c.String(200, "Success") } @@ -874,7 +878,7 @@ func startPage(c *gin.Context) { Test it with: ```sh -$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" +$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" ``` ### Bind Uri diff --git a/binding/binding_test.go b/binding/binding_test.go index 827518f..806f3ac 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -65,8 +65,15 @@ type FooStructUseNumber struct { } type FooBarStructForTimeType struct { - TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` - TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` + TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` + TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` +} + +type FooStructForTimeTypeNotUnixFormat struct { + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` } type FooStructForTimeTypeNotFormat struct { @@ -226,7 +233,10 @@ func TestBindingFormDefaultValue2(t *testing.T) { func TestBindingFormForTime(t *testing.T) { testFormBindingForTime(t, "POST", "/", "/", - "time_foo=2017-11-15&time_bar=", "bar2=foo") + "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo") + testFormBindingForTimeNotUnixFormat(t, "POST", + "/", "/", + "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, "POST", "/", "/", "time_foo=2017-11-15", "bar2=foo") @@ -240,8 +250,11 @@ func TestBindingFormForTime(t *testing.T) { func TestBindingFormForTime2(t *testing.T) { testFormBindingForTime(t, "GET", - "/?time_foo=2017-11-15&time_bar=", "/?bar2=foo", + "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo", "", "") + testFormBindingForTimeNotUnixFormat(t, "POST", + "/", "/", + "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, "GET", "/?time_foo=2017-11-15", "/?bar2=foo", "", "") @@ -849,6 +862,8 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String()) assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix()) assert.Equal(t, "UTC", obj.TimeBar.Location().String()) + assert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano()) + assert.Equal(t, int64(1562400033), obj.UnixTime.Unix()) obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) @@ -856,6 +871,24 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s assert.Error(t, err) } +func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, "form", b.Name()) + + obj := FooStructForTimeTypeNotUnixFormat{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = FooStructForTimeTypeNotUnixFormat{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index ebf3b19..80b1d15 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -266,6 +266,24 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val timeFormat = time.RFC3339 } + switch tf := strings.ToLower(timeFormat); tf { + case "unix", "unixnano": + tv, err := strconv.ParseInt(val, 10, 0) + if err != nil { + return err + } + + d := time.Duration(1) + if tf == "unixnano" { + d = time.Second + } + + t := time.Unix(tv/int64(d), tv%int64(d)) + value.Set(reflect.ValueOf(t)) + return nil + + } + if val == "" { value.Set(reflect.ValueOf(time.Time{})) return nil From 461df9320ac22d12d19a4e93894c54dd113b60c3 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 27 Jul 2019 03:06:37 +0200 Subject: [PATCH 379/912] Simplify code (#2004) - Use buf.String instead of converison - Remove redundant return --- gin.go | 1 - render/render_test.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gin.go b/gin.go index 220f040..cbdd080 100644 --- a/gin.go +++ b/gin.go @@ -437,7 +437,6 @@ func serveError(c *Context, code int, defaultMessage []byte) { return } c.writermem.WriteHeaderNow() - return } func redirectTrailingSlash(c *Context) { diff --git a/render/render_test.go b/render/render_test.go index 9d7eaee..acdb28a 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -45,7 +45,7 @@ func TestRenderMsgPack(t *testing.T) { err = codec.NewEncoder(buf, h).Encode(data) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), string(buf.Bytes())) + assert.Equal(t, w.Body.String(), buf.String()) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } From 20440b96b9ab70ef45346fb2a71f769586969442 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Mon, 5 Aug 2019 04:42:59 +0300 Subject: [PATCH 380/912] Support negative Content-Length in DataFromReader (#1981) You can get an http.Response with ContentLength set to -1 (Chunked encoding), so for DataFromReader to be useful for those we need to support that. --- render/reader.go | 4 +++- render/render_test.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/render/reader.go b/render/reader.go index 312af74..502d939 100644 --- a/render/reader.go +++ b/render/reader.go @@ -21,7 +21,9 @@ type Reader struct { // Render (Reader) writes data with custom ContentType and headers. func (r Reader) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) - r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) + if r.ContentLength >= 0 { + r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) + } r.writeHeaders(w, r.Headers) _, err = io.Copy(w, r.Reader) return diff --git a/render/render_test.go b/render/render_test.go index acdb28a..4cc7197 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -498,3 +498,26 @@ func TestRenderReader(t *testing.T) { assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } + +func TestRenderReaderNoContentLength(t *testing.T) { + w := httptest.NewRecorder() + + body := "#!PNG some raw data" + headers := make(map[string]string) + headers["Content-Disposition"] = `attachment; filename="filename.png"` + headers["x-request-id"] = "requestId" + + err := (Reader{ + ContentLength: -1, + ContentType: "image/png", + Reader: strings.NewReader(body), + Headers: headers, + }).Render(w) + + assert.NoError(t, err) + assert.Equal(t, body, w.Body.String()) + assert.Equal(t, "image/png", w.Header().Get("Content-Type")) + assert.NotContains(t, "Content-Length", w.Header()) + assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) + assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) +} From 5612cadb73b7f2415047e18f15824ed3855feac9 Mon Sep 17 00:00:00 2001 From: Andrew Szeto Date: Fri, 9 Aug 2019 18:26:58 -0700 Subject: [PATCH 381/912] Remove unused code (#2013) --- auth.go | 9 --------- auth_test.go | 7 ------- 2 files changed, 16 deletions(-) diff --git a/auth.go b/auth.go index 9ed81b5..c96b1e2 100644 --- a/auth.go +++ b/auth.go @@ -5,7 +5,6 @@ package gin import ( - "crypto/subtle" "encoding/base64" "net/http" "strconv" @@ -86,11 +85,3 @@ func authorizationHeader(user, password string) string { base := user + ":" + password return "Basic " + base64.StdEncoding.EncodeToString([]byte(base)) } - -func secureCompare(given, actual string) bool { - if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { - return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 - } - // Securely compare actual to itself to keep constant time, but always return false. - return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false -} diff --git a/auth_test.go b/auth_test.go index 197e920..e44bd10 100644 --- a/auth_test.go +++ b/auth_test.go @@ -81,13 +81,6 @@ func TestBasicAuthAuthorizationHeader(t *testing.T) { assert.Equal(t, "Basic YWRtaW46cGFzc3dvcmQ=", authorizationHeader("admin", "password")) } -func TestBasicAuthSecureCompare(t *testing.T) { - assert.True(t, secureCompare("1234567890", "1234567890")) - assert.False(t, secureCompare("123456789", "1234567890")) - assert.False(t, secureCompare("12345678900", "1234567890")) - assert.False(t, secureCompare("1234567891", "1234567890")) -} - func TestBasicAuthSucceed(t *testing.T) { accounts := Accounts{"admin": "password"} router := New() From 9a820cf0054bcd769f785457b7dbd149a7b29fdd Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Date: Thu, 15 Aug 2019 22:10:44 -0300 Subject: [PATCH 382/912] Bump github.com/mattn/go-isatty library to support Risc-V (#2019) Signed-off-by: CarlosEDP --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index dbe8afc..849f8c7 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/gin-contrib/sse v0.1.0 github.com/golang/protobuf v1.3.1 github.com/json-iterator/go v1.1.6 - github.com/mattn/go-isatty v0.0.8 + github.com/mattn/go-isatty v0.0.9 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/stretchr/testify v1.3.0 diff --git a/go.sum b/go.sum index c1e9f22..de17ae7 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= @@ -21,8 +21,8 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= From a22377b09b712d29078d5465391d336047719361 Mon Sep 17 00:00:00 2001 From: Shuo Date: Thu, 29 Aug 2019 08:32:22 +0800 Subject: [PATCH 383/912] logger_test: color (#1926) * logger color: string literals * logger_test: color --- logger.go | 21 +++++++++++---------- logger_test.go | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/logger.go b/logger.go index 5ab4639..fcf90c2 100644 --- a/logger.go +++ b/logger.go @@ -22,18 +22,19 @@ const ( forceColor ) -var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) - consoleColorMode = autoColor +const ( + green = "\033[97;42m" + white = "\033[90;47m" + yellow = "\033[90;43m" + red = "\033[97;41m" + blue = "\033[97;44m" + magenta = "\033[97;45m" + cyan = "\033[97;46m" + reset = "\033[0m" ) +var consoleColorMode = autoColor + // LoggerConfig defines the config for Logger middleware. type LoggerConfig struct { // Optional. Default value is gin.defaultLogFormatter diff --git a/logger_test.go b/logger_test.go index 9177e1d..fc53f35 100644 --- a/logger_test.go +++ b/logger_test.go @@ -291,14 +291,14 @@ func TestColorForMethod(t *testing.T) { return p.MethodColor() } - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForMethod("OPTIONS"), "options should be white") - assert.Equal(t, string([]byte{27, 91, 48, 109}), colorForMethod("TRACE"), "trace is not defined and should be the reset color") + assert.Equal(t, blue, colorForMethod("GET"), "get should be blue") + assert.Equal(t, cyan, colorForMethod("POST"), "post should be cyan") + assert.Equal(t, yellow, colorForMethod("PUT"), "put should be yellow") + assert.Equal(t, red, colorForMethod("DELETE"), "delete should be red") + assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green") + assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta") + assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white") + assert.Equal(t, reset, colorForMethod("TRACE"), "trace is not defined and should be the reset color") } func TestColorForStatus(t *testing.T) { @@ -309,10 +309,10 @@ func TestColorForStatus(t *testing.T) { return p.StatusCodeColor() } - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") + assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green") + assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white") + assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow") + assert.Equal(t, red, colorForStatus(2), "other things should be red") } func TestResetColor(t *testing.T) { From 6ece26c7c5ce36863599c9a897514e2a70d4021c Mon Sep 17 00:00:00 2001 From: Johnny Dallas Date: Thu, 29 Aug 2019 19:58:55 -0700 Subject: [PATCH 384/912] Add Header bind methods to README (#2025) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2761259..529746a 100644 --- a/README.md +++ b/README.md @@ -622,10 +622,10 @@ Note that you need to set the corresponding binding tag on all fields you want t Also, Gin provides two sets of methods for binding: - **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader` - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Type** - Should bind - - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML` + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader` - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. From 01ca625b98910363175cd24b0093c6f14e9d6dd3 Mon Sep 17 00:00:00 2001 From: George Gabolaev Date: Mon, 2 Sep 2019 15:18:08 +0300 Subject: [PATCH 385/912] Fixed JSONP format (added semicolon) (#2007) * Fixed JSONP format (added semicolon) * render_test fix --- context_test.go | 2 +- render/json.go | 2 +- render/render_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index 439e8ee..f7bb0f5 100644 --- a/context_test.go +++ b/context_test.go @@ -676,7 +676,7 @@ func TestContextRenderJSONP(t *testing.T) { c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) + assert.Equal(t, "x({\"foo\":\"bar\"});", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/render/json.go b/render/json.go index 2b07cba..70506f7 100644 --- a/render/json.go +++ b/render/json.go @@ -138,7 +138,7 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { if err != nil { return err } - _, err = w.Write([]byte(")")) + _, err = w.Write([]byte(");")) if err != nil { return err } diff --git a/render/render_test.go b/render/render_test.go index 4cc7197..b27134f 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -146,7 +146,7 @@ func TestRenderJsonpJSON(t *testing.T) { err1 := (JsonpJSON{"x", data}).Render(w1) assert.NoError(t, err1) - assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String()) + assert.Equal(t, "x({\"foo\":\"bar\"});", w1.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() @@ -158,7 +158,7 @@ func TestRenderJsonpJSON(t *testing.T) { err2 := (JsonpJSON{"x", datas}).Render(w2) assert.NoError(t, err2) - assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String()) + assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}]);", w2.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } From c3f7fc399a11d746ce2147b0c9165f57058d18ac Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 4 Sep 2019 12:26:50 +0800 Subject: [PATCH 386/912] chore: support go1.13 (#2038) * chore: support go1.13 * chore: remove env var for go1.13 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 27c80ef..8b3b5a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ matrix: env: GO111MODULE=on - go: 1.12.x env: GO111MODULE=on + - go: 1.13.x - go: master env: GO111MODULE=on From 1acb3fb30ac6e2d452c509fdffaf6cfa2fafc39b Mon Sep 17 00:00:00 2001 From: thinkerou Date: Thu, 5 Sep 2019 21:39:56 +0800 Subject: [PATCH 387/912] upgrade validator version to v9 (#1015) * upgrade validator version to v9 * Update vendor.json * Update go.mod * Update go.sum * fix * fix * fix bug * Update binding_test.go * Update validate_test.go * Update go.sum * Update go.mod * Update go.sum * Update go.mod * Update go.sum --- binding/binding_test.go | 8 +++---- binding/default_validator.go | 6 +++--- binding/validate_test.go | 14 +++--------- go.mod | 13 +++++------ go.sum | 32 ++++++++++++++++----------- vendor/vendor.json | 42 ++++++++++++++++++++++++++++++------ 6 files changed, 73 insertions(+), 42 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 806f3ac..3d08d69 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -658,9 +658,9 @@ func TestValidationDisabled(t *testing.T) { assert.NoError(t, err) } -func TestExistsSucceeds(t *testing.T) { +func TestRequiredSucceeds(t *testing.T) { type HogeStruct struct { - Hoge *int `json:"hoge" binding:"exists"` + Hoge *int `json:"hoge" binding:"required"` } var obj HogeStruct @@ -669,9 +669,9 @@ func TestExistsSucceeds(t *testing.T) { assert.NoError(t, err) } -func TestExistsFails(t *testing.T) { +func TestRequiredFails(t *testing.T) { type HogeStruct struct { - Hoge *int `json:"foo" binding:"exists"` + Hoge *int `json:"foo" binding:"required"` } var obj HogeStruct diff --git a/binding/default_validator.go b/binding/default_validator.go index e7a302d..50e0d57 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -8,7 +8,7 @@ import ( "reflect" "sync" - "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v9" ) type defaultValidator struct { @@ -45,7 +45,7 @@ func (v *defaultValidator) Engine() interface{} { func (v *defaultValidator) lazyinit() { v.once.Do(func() { - config := &validator.Config{TagName: "binding"} - v.validate = validator.New(config) + v.validate = validator.New() + v.validate.SetTagName("binding") }) } diff --git a/binding/validate_test.go b/binding/validate_test.go index 2c76b6d..81f7883 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -6,12 +6,11 @@ package binding import ( "bytes" - "reflect" "testing" "time" "github.com/stretchr/testify/assert" - "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v9" ) type testInterface interface { @@ -200,15 +199,8 @@ type structCustomValidation struct { Integer int `binding:"notone"` } -// notOne is a custom validator meant to be used with `validator.v8` library. -// The method signature for `v9` is significantly different and this function -// would need to be changed for tests to pass after upgrade. -// See https://github.com/gin-gonic/gin/pull/1015. -func notOne( - v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, - field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, -) bool { - if val, ok := field.Interface().(int); ok { +func notOne(f1 validator.FieldLevel) bool { + if val, ok := f1.Field().Interface().(int); ok { return val != 1 } return false diff --git a/go.mod b/go.mod index 849f8c7..3415185 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,15 @@ go 1.12 require ( github.com/gin-contrib/sse v0.1.0 - github.com/golang/protobuf v1.3.1 - github.com/json-iterator/go v1.1.6 + github.com/go-playground/locales v0.12.1 // indirect + github.com/go-playground/universal-translator v0.16.0 // indirect + github.com/golang/protobuf v1.3.2 + github.com/json-iterator/go v1.1.7 + github.com/leodido/go-urn v1.1.0 // indirect github.com/mattn/go-isatty v0.0.9 - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/stretchr/testify v1.3.0 + github.com/stretchr/testify v1.4.0 github.com/ugorji/go/codec v1.1.7 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect - gopkg.in/go-playground/validator.v8 v8.18.2 + gopkg.in/go-playground/validator.v9 v9.29.1 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index de17ae7..7b4ee32 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,30 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= @@ -27,7 +35,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/vendor.json b/vendor/vendor.json index fa8fd13..d441d4a 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -18,6 +18,28 @@ "version": "v0.1", "versionExact": "v0.1.0" }, + { + "checksumSHA1": "b4DmyMT9bicTRVJw1hJXHLhIH+0=", + "path": "github.com/go-playground/locales", + "revision": "f63010822830b6fe52288ee52d5a1151088ce039", + "revisionTime": "2018-03-23T16:04:04Z", + "version": "v0.12", + "versionExact": "v0.12.1" + }, + { + "checksumSHA1": "JgF260rC9YpWyY5WEljjimWLUXs=", + "path": "github.com/go-playground/locales/currency", + "revision": "630ebbb602847eba93e75ae38bbc7bb7abcf1ff3", + "revisionTime": "2019-04-30T15:33:29Z" + }, + { + "checksumSHA1": "9pKcUHBaVS+360X6h4IowhmOPjk=", + "path": "github.com/go-playground/universal-translator", + "revision": "b32fa301c9fe55953584134cb6853a13c87ec0a1", + "revisionTime": "2017-02-09T16:11:52Z", + "version": "v0.16", + "versionExact": "v0.16.0" + }, { "checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=", "path": "github.com/golang/protobuf/proto", @@ -26,6 +48,14 @@ "version": "v1.3", "versionExact": "v1.3.0" }, + { + "checksumSHA1": "zNo6yGy/bCJuzkEcP70oEBtOB2M=", + "path": "github.com/leodido/go-urn", + "revision": "70078a794e8ea4b497ba7c19a78cd60f90ccf0f4", + "revisionTime": "2018-05-24T03:26:21Z", + "version": "v1.1", + "versionExact": "v1.1.0" + }, { "checksumSHA1": "TB2vxux9xQbvsTHOVt4aRTuvSn4=", "path": "github.com/json-iterator/go", @@ -97,12 +127,12 @@ "revisionTime": "2019-05-02T15:41:39Z" }, { - "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", - "path": "gopkg.in/go-playground/validator.v8", - "revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf", - "revisionTime": "2017-07-30T05:02:35Z", - "version": "v8.18.2", - "versionExact": "v8.18.2" + "checksumSHA1": "ACzc7AkwLtNgKhqtj8V7SGUJgnw=", + "path": "gopkg.in/go-playground/validator.v9", + "revision": "46b4b1e301c24cac870ffcb4ba5c8a703d1ef475", + "revisionTime": "2019-03-31T13:31:25Z", + "version": "v9.28", + "versionExact": "v9.28.0" }, { "checksumSHA1": "QqDq2x8XOU7IoOR98Cx1eiV5QY8=", From b80d67586488cc379781cd8a13ac267e4d966e35 Mon Sep 17 00:00:00 2001 From: Jim Filippou Date: Thu, 5 Sep 2019 16:50:54 +0300 Subject: [PATCH 388/912] Added specific installation instructions for Mac (#2011) Made it more clear for Mac users using Go version 1.8 and greater. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 529746a..b488f15 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,12 @@ $ go get github.com/kardianos/govendor $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" ``` +If you are on a Mac and you're installing Go 1.8 (released: Feb 2017) or later, GOPATH is automatically determined by the Go toolchain for you. It defaults to $HOME/go on macOS so you can create your project like this + +```sh +$ mkdir -p $HOME/go/src/github.com/myusername/project && cd "$_" +``` + 3. Vendor init your project and add gin ```sh From f38c30a0d2f56c54397543f08902b00260c70ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Szafra=C5=84ski?= Date: Fri, 6 Sep 2019 07:56:59 +0200 Subject: [PATCH 389/912] feat(binding): add DisallowUnknownFields() in gin.Context.BindJSON() (#2028) --- binding/binding_test.go | 29 +++++++++++++++++++++++++++++ binding/json.go | 9 +++++++++ mode.go | 8 +++++++- mode_test.go | 6 ++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 3d08d69..caabaac 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -64,6 +64,10 @@ type FooStructUseNumber struct { Foo interface{} `json:"foo" binding:"required"` } +type FooStructDisallowUnknownFields struct { + Foo interface{} `json:"foo" binding:"required"` +} + type FooBarStructForTimeType struct { TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` @@ -194,6 +198,12 @@ func TestBindingJSONUseNumber2(t *testing.T) { `{"foo": 123}`, `{"bar": "foo"}`) } +func TestBindingJSONDisallowUnknownFields(t *testing.T) { + testBodyBindingDisallowUnknownFields(t, JSON, + "/", "/", + `{"foo": "bar"}`, `{"foo": "bar", "what": "this"}`) +} + func TestBindingForm(t *testing.T) { testFormBinding(t, "POST", "/", "/", @@ -1162,6 +1172,25 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod assert.Error(t, err) } +func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath, body, badBody string) { + EnableDecoderDisallowUnknownFields = true + defer func() { + EnableDecoderDisallowUnknownFields = false + }() + + obj := FooStructDisallowUnknownFields{} + req := requestWithBody("POST", path, body) + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, "bar", obj.Foo) + + obj = FooStructDisallowUnknownFields{} + req = requestWithBody("POST", badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) + assert.Contains(t, err.Error(), "what") +} + func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) diff --git a/binding/json.go b/binding/json.go index f968161..d62e070 100644 --- a/binding/json.go +++ b/binding/json.go @@ -18,6 +18,12 @@ import ( // interface{} as a Number instead of as a float64. var EnableDecoderUseNumber = false +// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method +// on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to +// return an error when the destination is a struct and the input contains object +// keys which do not match any non-ignored, exported fields in the destination. +var EnableDecoderDisallowUnknownFields = false + type jsonBinding struct{} func (jsonBinding) Name() string { @@ -40,6 +46,9 @@ func decodeJSON(r io.Reader, obj interface{}) error { if EnableDecoderUseNumber { decoder.UseNumber() } + if EnableDecoderDisallowUnknownFields { + decoder.DisallowUnknownFields() + } if err := decoder.Decode(obj); err != nil { return err } diff --git a/mode.go b/mode.go index 8aa84aa..c3c37fd 100644 --- a/mode.go +++ b/mode.go @@ -71,12 +71,18 @@ func DisableBindValidation() { binding.Validator = nil } -// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to +// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumber to // call the UseNumber method on the JSON Decoder instance. func EnableJsonDecoderUseNumber() { binding.EnableDecoderUseNumber = true } +// EnableJsonDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to +// call the DisallowUnknownFields method on the JSON Decoder instance. +func EnableJsonDecoderDisallowUnknownFields() { + binding.EnableDecoderDisallowUnknownFields = true +} + // Mode returns currently gin mode. func Mode() string { return modeName diff --git a/mode_test.go b/mode_test.go index 3dba515..0c5a323 100644 --- a/mode_test.go +++ b/mode_test.go @@ -45,3 +45,9 @@ func TestEnableJsonDecoderUseNumber(t *testing.T) { EnableJsonDecoderUseNumber() assert.True(t, binding.EnableDecoderUseNumber) } + +func TestEnableJsonDecoderDisallowUnknownFields(t *testing.T) { + assert.False(t, binding.EnableDecoderDisallowUnknownFields) + EnableJsonDecoderDisallowUnknownFields() + assert.True(t, binding.EnableDecoderDisallowUnknownFields) +} From b8b2fada5c90fad166b77ec9c2a535dbe76ab8a1 Mon Sep 17 00:00:00 2001 From: Panmax <967168@qq.com> Date: Tue, 10 Sep 2019 14:32:30 +0800 Subject: [PATCH 390/912] fix GetPostFormMap (#2051) --- context.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/context.go b/context.go index d9fcc28..86094ca 100644 --- a/context.go +++ b/context.go @@ -491,13 +491,8 @@ func (c *Context) PostFormMap(key string) map[string]string { // GetPostFormMap returns a map for a given form key, plus a boolean value // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { - req := c.Request - if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { - if err != http.ErrNotMultipart { - debugPrint("error on parse multipart form map: %v", err) - } - } - return c.get(req.PostForm, key) + c.getFormCache() + return c.get(c.formCache, key) } // get is an internal method and returns a map which satisfy conditions. From 9aa870f108a1dab29abeba1f6289357a327e53d1 Mon Sep 17 00:00:00 2001 From: Gaozhen Ying Date: Tue, 10 Sep 2019 17:16:37 +0800 Subject: [PATCH 391/912] Adjust Render.Redirect test case (#2053) --- render/render_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/render/render_test.go b/render/render_test.go index b27134f..95a01b6 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -347,7 +347,17 @@ func TestRenderRedirect(t *testing.T) { } w = httptest.NewRecorder() - assert.Panics(t, func() { assert.NoError(t, data2.Render(w)) }) + assert.PanicsWithValue(t, "Cannot redirect with status code 200", func() { data2.Render(w) }) + + data3 := Redirect{ + Code: http.StatusCreated, + Request: req, + Location: "/new/location", + } + + w = httptest.NewRecorder() + err = data3.Render(w) + assert.NoError(t, err) // only improve coverage data2.WriteContentType(w) From b562fed3aa28c6e6d0299406d6b06bcb16a498cc Mon Sep 17 00:00:00 2001 From: ZYunH Date: Wed, 11 Sep 2019 18:10:39 +0800 Subject: [PATCH 392/912] Make countParams more readable (#2052) --- tree.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tree.go b/tree.go index 371d5ad..4194757 100644 --- a/tree.go +++ b/tree.go @@ -65,10 +65,9 @@ func min(a, b int) int { func countParams(path string) uint8 { var n uint for i := 0; i < len(path); i++ { - if path[i] != ':' && path[i] != '*' { - continue + if path[i] == ':' || path[i] == '*' { + n++ } - n++ } if n >= 255 { return 255 From 0b96dd8ae554b8131de6b354e300ee6cf8e56f69 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 22 Sep 2019 15:35:34 +0800 Subject: [PATCH 393/912] chore: remove env var for go master branch (#2056) --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8b3b5a2..748a07a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ matrix: env: GO111MODULE=on - go: 1.13.x - go: master - env: GO111MODULE=on git: depth: 10 From f45c83c70cb27a16d3cae220afff2a731ea4f707 Mon Sep 17 00:00:00 2001 From: bullgare Date: Mon, 23 Sep 2019 18:48:10 +0300 Subject: [PATCH 394/912] Updated Readme.md for serving multiple services (#2067) Previous version had issues - if one service did not start for any reason, you would never know about it. --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b488f15..dfedd8a 100644 --- a/README.md +++ b/README.md @@ -1678,11 +1678,19 @@ func main() { } g.Go(func() error { - return server01.ListenAndServe() + err := server01.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + return err }) g.Go(func() error { - return server02.ListenAndServe() + err := server02.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + return err }) if err := g.Wait(); err != nil { From 2e5a7196cce94e085625d166e7f2cf9f99a1edf0 Mon Sep 17 00:00:00 2001 From: Santhosh Kumar Date: Tue, 24 Sep 2019 07:31:57 +0530 Subject: [PATCH 395/912] use url.URL.Query instead of parsing query (#2063) --- context.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/context.go b/context.go index 86094ca..509ce08 100644 --- a/context.go +++ b/context.go @@ -393,8 +393,7 @@ func (c *Context) QueryArray(key string) []string { func (c *Context) getQueryCache() { if c.queryCache == nil { - c.queryCache = make(url.Values) - c.queryCache, _ = url.ParseQuery(c.Request.URL.RawQuery) + c.queryCache = c.Request.URL.Query() } } From d6eafcf48abbf3895df5ae5019682b7ae1f1ca2e Mon Sep 17 00:00:00 2001 From: Gaozhen Ying Date: Tue, 24 Sep 2019 21:44:15 +0800 Subject: [PATCH 396/912] add TestDisableBindValidation (#2071) --- mode_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mode_test.go b/mode_test.go index 0c5a323..1b5fb2f 100644 --- a/mode_test.go +++ b/mode_test.go @@ -40,6 +40,14 @@ func TestSetMode(t *testing.T) { assert.Panics(t, func() { SetMode("unknown") }) } +func TestDisableBindValidation(t *testing.T) { + v := binding.Validator + assert.NotNil(t, binding.Validator) + DisableBindValidation() + assert.Nil(t, binding.Validator) + binding.Validator = v +} + func TestEnableJsonDecoderUseNumber(t *testing.T) { assert.False(t, binding.EnableDecoderUseNumber) EnableJsonDecoderUseNumber() From 9b9f4fab34cc3e47e3c7e390d2e2d9c11276d9b3 Mon Sep 17 00:00:00 2001 From: bullgare Date: Tue, 24 Sep 2019 17:18:41 +0300 Subject: [PATCH 397/912] Updated Readme.md: file.Close() for template read (#2068) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dfedd8a..959848d 100644 --- a/README.md +++ b/README.md @@ -1807,6 +1807,7 @@ func main() { func loadTemplate() (*template.Template, error) { t := template.New("") for name, file := range Assets.Files { + defer file.Close() if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { continue } From 79840bc1c62d7d6104e2b1c5d39099f92f9f8d11 Mon Sep 17 00:00:00 2001 From: Manjusaka Date: Mon, 30 Sep 2019 09:12:22 +0800 Subject: [PATCH 398/912] support run HTTP server with specific net.Listener (#2023) --- gin.go | 9 +++++++++ gin_integration_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/gin.go b/gin.go index cbdd080..894cf09 100644 --- a/gin.go +++ b/gin.go @@ -338,6 +338,15 @@ func (engine *Engine) RunFd(fd int) (err error) { return } defer listener.Close() + err = engine.RunListener(listener) + return +} + +// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests +// through the specified net.Listener +func (engine *Engine) RunListener(listener net.Listener) (err error) { + debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr()) + defer func() { debugPrintError(err) }() err = http.Serve(listener, engine) return } diff --git a/gin_integration_test.go b/gin_integration_test.go index 9beec14..7e270b9 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -207,6 +207,42 @@ func TestBadFileDescriptor(t *testing.T) { assert.Error(t, router.RunFd(0)) } +func TestListener(t *testing.T) { + router := New() + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + assert.NoError(t, err) + listener, err := net.ListenTCP("tcp", addr) + assert.NoError(t, err) + go func() { + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + assert.NoError(t, router.RunListener(listener)) + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + c, err := net.Dial("tcp", listener.Addr().String()) + assert.NoError(t, err) + + fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") + scanner := bufio.NewScanner(c) + var response string + for scanner.Scan() { + response += scanner.Text() + } + assert.Contains(t, response, "HTTP/1.0 200", "should get a 200") + assert.Contains(t, response, "it worked", "resp body should match") +} + +func TestBadListener(t *testing.T) { + router := New() + addr, err := net.ResolveTCPAddr("tcp", "localhost:10086") + assert.NoError(t, err) + listener, err := net.ListenTCP("tcp", addr) + listener.Close() + assert.Error(t, router.RunListener(listener)) +} + func TestWithHttptestWithAutoSelectedPort(t *testing.T) { router := New() router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) From beb879e4754af8d25b9f326c20d831e518ad645f Mon Sep 17 00:00:00 2001 From: John Bampton Date: Mon, 30 Sep 2019 16:22:12 +1000 Subject: [PATCH 399/912] Change Writter to Writer. (#2079) --- response_writer_test.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/response_writer_test.go b/response_writer_test.go index a5e111e..1f113e7 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -29,38 +29,38 @@ func init() { } func TestResponseWriterReset(t *testing.T) { - testWritter := httptest.NewRecorder() + testWriter := httptest.NewRecorder() writer := &responseWriter{} var w ResponseWriter = writer - writer.reset(testWritter) + writer.reset(testWriter) assert.Equal(t, -1, writer.size) assert.Equal(t, http.StatusOK, writer.status) - assert.Equal(t, testWritter, writer.ResponseWriter) + assert.Equal(t, testWriter, writer.ResponseWriter) assert.Equal(t, -1, w.Size()) assert.Equal(t, http.StatusOK, w.Status()) assert.False(t, w.Written()) } func TestResponseWriterWriteHeader(t *testing.T) { - testWritter := httptest.NewRecorder() + testWriter := httptest.NewRecorder() writer := &responseWriter{} - writer.reset(testWritter) + writer.reset(testWriter) w := ResponseWriter(writer) w.WriteHeader(http.StatusMultipleChoices) assert.False(t, w.Written()) assert.Equal(t, http.StatusMultipleChoices, w.Status()) - assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code) + assert.NotEqual(t, http.StatusMultipleChoices, testWriter.Code) w.WriteHeader(-1) assert.Equal(t, http.StatusMultipleChoices, w.Status()) } func TestResponseWriterWriteHeadersNow(t *testing.T) { - testWritter := httptest.NewRecorder() + testWriter := httptest.NewRecorder() writer := &responseWriter{} - writer.reset(testWritter) + writer.reset(testWriter) w := ResponseWriter(writer) w.WriteHeader(http.StatusMultipleChoices) @@ -68,7 +68,7 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) { assert.True(t, w.Written()) assert.Equal(t, 0, w.Size()) - assert.Equal(t, http.StatusMultipleChoices, testWritter.Code) + assert.Equal(t, http.StatusMultipleChoices, testWriter.Code) writer.size = 10 w.WriteHeaderNow() @@ -76,30 +76,30 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) { } func TestResponseWriterWrite(t *testing.T) { - testWritter := httptest.NewRecorder() + testWriter := httptest.NewRecorder() writer := &responseWriter{} - writer.reset(testWritter) + writer.reset(testWriter) w := ResponseWriter(writer) n, err := w.Write([]byte("hola")) assert.Equal(t, 4, n) assert.Equal(t, 4, w.Size()) assert.Equal(t, http.StatusOK, w.Status()) - assert.Equal(t, http.StatusOK, testWritter.Code) - assert.Equal(t, "hola", testWritter.Body.String()) + assert.Equal(t, http.StatusOK, testWriter.Code) + assert.Equal(t, "hola", testWriter.Body.String()) assert.NoError(t, err) n, err = w.Write([]byte(" adios")) assert.Equal(t, 6, n) assert.Equal(t, 10, w.Size()) - assert.Equal(t, "hola adios", testWritter.Body.String()) + assert.Equal(t, "hola adios", testWriter.Body.String()) assert.NoError(t, err) } func TestResponseWriterHijack(t *testing.T) { - testWritter := httptest.NewRecorder() + testWriter := httptest.NewRecorder() writer := &responseWriter{} - writer.reset(testWritter) + writer.reset(testWriter) w := ResponseWriter(writer) assert.Panics(t, func() { From 4fd3234840dbfec7b619f70f341e339f66604cfd Mon Sep 17 00:00:00 2001 From: John Bampton Date: Thu, 3 Oct 2019 09:46:41 +1000 Subject: [PATCH 400/912] Fix spelling. (#2080) --- CHANGELOG.md | 6 +++--- README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15dfb1a..6ccd2fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ - [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749) - [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252) - [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775) -- [NEW] Extend context.File to allow for the content-dispositon attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260) +- [NEW] Extend context.File to allow for the content-disposition attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260) - [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112) - [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238) - [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020) @@ -231,7 +231,7 @@ - [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults. - [NEW] Flexible rendering API - [NEW] Add Context.File() -- [NEW] Add shorcut RunTLS() for http.ListenAndServeTLS +- [NEW] Add shortcut RunTLS() for http.ListenAndServeTLS - [FIX] Rename NotFound404() to NoRoute() - [FIX] Errors in context are purged - [FIX] Adds HEAD method in Static file serving @@ -254,7 +254,7 @@ - [NEW] New Bind() and BindWith() methods for parsing request body. - [NEW] Add Content.Copy() - [NEW] Add context.LastError() -- [NEW] Add shorcut for OPTIONS HTTP method +- [NEW] Add shortcut for OPTIONS HTTP method - [FIX] Tons of README fixes - [FIX] Header is written before body - [FIX] BasicAuth() and changes API a little bit diff --git a/README.md b/README.md index 959848d..22f83b6 100644 --- a/README.md +++ b/README.md @@ -1149,7 +1149,7 @@ func main() { #### AsciiJSON -Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters. +Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters. ```go func main() { From f7becac7bc7290c23174ebbaf510db545660bb8e Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Thu, 10 Oct 2019 10:58:31 +0200 Subject: [PATCH 401/912] Relocate binding body tests (#2086) * Relocate binding body tests Every test file should be related to a tested file. Remove useless tests. * Add github.com/stretchr/testify/require package --- binding/binding_body_test.go | 72 ------------------------------------ binding/json_test.go | 21 +++++++++++ binding/msgpack_test.go | 32 ++++++++++++++++ binding/xml_test.go | 25 +++++++++++++ binding/yaml_test.go | 21 +++++++++++ vendor/vendor.json | 6 +++ 6 files changed, 105 insertions(+), 72 deletions(-) delete mode 100644 binding/binding_body_test.go create mode 100644 binding/json_test.go create mode 100644 binding/msgpack_test.go create mode 100644 binding/xml_test.go create mode 100644 binding/yaml_test.go diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go deleted file mode 100644 index 901d429..0000000 --- a/binding/binding_body_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package binding - -import ( - "bytes" - "io/ioutil" - "testing" - - "github.com/gin-gonic/gin/testdata/protoexample" - "github.com/golang/protobuf/proto" - "github.com/stretchr/testify/assert" - "github.com/ugorji/go/codec" -) - -func TestBindingBody(t *testing.T) { - for _, tt := range []struct { - name string - binding BindingBody - body string - want string - }{ - { - name: "JSON binding", - binding: JSON, - body: `{"foo":"FOO"}`, - }, - { - name: "XML binding", - binding: XML, - body: ` - - FOO -`, - }, - { - name: "MsgPack binding", - binding: MsgPack, - body: msgPackBody(t), - }, - { - name: "YAML binding", - binding: YAML, - body: `foo: FOO`, - }, - } { - t.Logf("testing: %s", tt.name) - req := requestWithBody("POST", "/", tt.body) - form := FooStruct{} - body, _ := ioutil.ReadAll(req.Body) - assert.NoError(t, tt.binding.BindBody(body, &form)) - assert.Equal(t, FooStruct{"FOO"}, form) - } -} - -func msgPackBody(t *testing.T) string { - test := FooStruct{"FOO"} - h := new(codec.MsgpackHandle) - buf := bytes.NewBuffer(nil) - assert.NoError(t, codec.NewEncoder(buf, h).Encode(test)) - return buf.String() -} - -func TestBindingBodyProto(t *testing.T) { - test := protoexample.Test{ - Label: proto.String("FOO"), - } - data, _ := proto.Marshal(&test) - req := requestWithBody("POST", "/", string(data)) - form := protoexample.Test{} - body, _ := ioutil.ReadAll(req.Body) - assert.NoError(t, ProtoBuf.BindBody(body, &form)) - assert.Equal(t, test, form) -} diff --git a/binding/json_test.go b/binding/json_test.go new file mode 100644 index 0000000..cae4ccc --- /dev/null +++ b/binding/json_test.go @@ -0,0 +1,21 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestJSONBindingBindBody(t *testing.T) { + var s struct { + Foo string `json:"foo"` + } + err := jsonBinding{}.BindBody([]byte(`{"foo": "FOO"}`), &s) + require.NoError(t, err) + assert.Equal(t, "FOO", s.Foo) +} diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go new file mode 100644 index 0000000..6baa673 --- /dev/null +++ b/binding/msgpack_test.go @@ -0,0 +1,32 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/ugorji/go/codec" +) + +func TestMsgpackBindingBindBody(t *testing.T) { + type teststruct struct { + Foo string `msgpack:"foo"` + } + var s teststruct + err := msgpackBinding{}.BindBody(msgpackBody(t, teststruct{"FOO"}), &s) + require.NoError(t, err) + assert.Equal(t, "FOO", s.Foo) +} + +func msgpackBody(t *testing.T, obj interface{}) []byte { + var bs bytes.Buffer + h := &codec.MsgpackHandle{} + err := codec.NewEncoder(&bs, h).Encode(obj) + require.NoError(t, err) + return bs.Bytes() +} diff --git a/binding/xml_test.go b/binding/xml_test.go new file mode 100644 index 0000000..f9546c1 --- /dev/null +++ b/binding/xml_test.go @@ -0,0 +1,25 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestXMLBindingBindBody(t *testing.T) { + var s struct { + Foo string `xml:"foo"` + } + xmlBody := ` + + FOO +` + err := xmlBinding{}.BindBody([]byte(xmlBody), &s) + require.NoError(t, err) + assert.Equal(t, "FOO", s.Foo) +} diff --git a/binding/yaml_test.go b/binding/yaml_test.go new file mode 100644 index 0000000..e66338b --- /dev/null +++ b/binding/yaml_test.go @@ -0,0 +1,21 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestYAMLBindingBindBody(t *testing.T) { + var s struct { + Foo string `yaml:"foo"` + } + err := yamlBinding{}.BindBody([]byte("foo: FOO"), &s) + require.NoError(t, err) + assert.Equal(t, "FOO", s.Foo) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index d441d4a..70b2d9e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -112,6 +112,12 @@ "version": "v1.2", "versionExact": "v1.2.2" }, + { + "checksumSHA1": "wnEANt4k5X/KGwoFyfSSnpxULm4=", + "path": "github.com/stretchr/testify/require", + "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", + "revisionTime": "2018-05-06T18:05:49Z" + }, { "checksumSHA1": "S4ei9eSqVThDio0Jn2sav6yUbvg=", "path": "github.com/ugorji/go/codec", From 3cea16cc6c9391224d122fe303b0dc81454acbd2 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Tue, 15 Oct 2019 05:04:25 +0200 Subject: [PATCH 402/912] Update go.sum file (#2094) --- go.sum | 1 + 1 file changed, 1 insertion(+) diff --git a/go.sum b/go.sum index 7b4ee32..129ad38 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= From 1a1cf655bd72f769e680270f2d39e43f1b82b2ad Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Tue, 15 Oct 2019 08:25:55 +0200 Subject: [PATCH 403/912] add details in issue template (#2085) indirectly request more details --- .github/ISSUE_TEMPLATE.md | 46 ++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9d49aa4..6f8288d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,11 +3,47 @@ - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. -- go version: -- gin version (or commit ref): -- operating system: - ## Description -## Screenshots + + +## How to reproduce + + +``` +package main + +import ( + "github.com/gin-gonic/gin" +) + +func main() { + g := gin.Default() + g.GET("/hello/:name", func(c *gin.Context) { + c.String(200, "Hello %s", c.Param("name")) + }) + g.Run(":9000") +} +``` +## Expectations + + +``` +$ curl http://localhost:8201/hello/world +Hello world +``` + +## Actual result + + +``` +$ curl -i http://localhost:8201/hello/world + +``` + +## Environment + +- go version: +- gin version (or commit ref): +- operating system: From 0ce46610292cc8877a914b1cee41acd5dc9da7ae Mon Sep 17 00:00:00 2001 From: willnewrelic Date: Wed, 16 Oct 2019 19:14:44 -0700 Subject: [PATCH 404/912] Use Writer in Context.Status (#1606) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 509ce08..18d328d 100644 --- a/context.go +++ b/context.go @@ -744,7 +744,7 @@ func bodyAllowedForStatus(status int) bool { // Status sets the HTTP response code. func (c *Context) Status(code int) { - c.writermem.WriteHeader(code) + c.Writer.WriteHeader(code) } // Header is a intelligent shortcut for c.Writer.Header().Set(key, value). From 089016a09297b7acc16b3df2ef2add1880b17502 Mon Sep 17 00:00:00 2001 From: Ildar1111 <54001462+Ildar1111@users.noreply.github.com> Date: Fri, 25 Oct 2019 05:03:53 +0300 Subject: [PATCH 405/912] Update README.md (#2106) * Update README.md c:\>curl 0.0.0.0:8080 "Failed to connect to 0.0.0.0 port 8080: Address not available" Connecting to address 0.0.0.0:8080 is not allowed on windows. From http://msdn.microsoft.com/en-us/library/aa923167.aspx " ... If the address member of the structure specified by the name parameter is all zeroes, connect will return the error WSAEADDRNOTAVAIL. ..." * Update README.md edit comment --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 22f83b6..3dd99d9 100644 --- a/README.md +++ b/README.md @@ -145,12 +145,12 @@ func main() { "message": "pong", }) }) - r.Run() // listen and serve on 0.0.0.0:8080 + r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") } ``` ``` -# run example.go and visit 0.0.0.0:8080/ping on browser +# run example.go and visit 0.0.0.0:8080/ping (for windows "localhost:8080/ping") on browser $ go run example.go ``` From 8a1bfcfd3b8b514f5cd9d36b575af204a86ddfb0 Mon Sep 17 00:00:00 2001 From: ZhangYunHao Date: Sat, 26 Oct 2019 14:20:35 +0800 Subject: [PATCH 406/912] format errUnknownType (#2103) --- binding/form_mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 80b1d15..ec78bfe 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -15,7 +15,7 @@ import ( "github.com/gin-gonic/gin/internal/json" ) -var errUnknownType = errors.New("Unknown type") +var errUnknownType = errors.New("unknown type") func mapUri(ptr interface{}, m map[string][]string) error { return mapFormByTag(ptr, m, "uri") From 393a63f3b020df89d42695064443760c7d0a0dc8 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Sun, 27 Oct 2019 06:58:59 +0100 Subject: [PATCH 407/912] Fix 'errcheck' linter warnings (#2093) --- binding/binding_test.go | 9 ++++++--- binding/form_mapping_benchmark_test.go | 10 ++++++++-- gin.go | 5 ++++- gin_integration_test.go | 4 +++- render/render_test.go | 5 ++++- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index caabaac..f0b6f79 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -441,7 +441,8 @@ func createFormFilesMultipartRequest(t *testing.T) *http.Request { defer f.Close() fw, err1 := mw.CreateFormFile("file", "form.go") assert.NoError(t, err1) - io.Copy(fw, f) + _, err = io.Copy(fw, f) + assert.NoError(t, err) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) assert.NoError(t, err2) @@ -465,7 +466,8 @@ func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { defer f.Close() fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go") assert.NoError(t, err1) - io.Copy(fw, f) + _, err = io.Copy(fw, f) + assert.NoError(t, err) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) assert.NoError(t, err2) @@ -554,7 +556,8 @@ func TestBindingFormPostForMapFail(t *testing.T) { func TestBindingFormFilesMultipart(t *testing.T) { req := createFormFilesMultipartRequest(t) var obj FooBarFileStruct - FormMultipart.Bind(req, &obj) + err := FormMultipart.Bind(req, &obj) + assert.NoError(t, err) // file from os f, _ := os.Open("form.go") diff --git a/binding/form_mapping_benchmark_test.go b/binding/form_mapping_benchmark_test.go index 0ef08f0..9572ea0 100644 --- a/binding/form_mapping_benchmark_test.go +++ b/binding/form_mapping_benchmark_test.go @@ -32,7 +32,10 @@ type structFull struct { func BenchmarkMapFormFull(b *testing.B) { var s structFull for i := 0; i < b.N; i++ { - mapForm(&s, form) + err := mapForm(&s, form) + if err != nil { + b.Fatalf("Error on a form mapping") + } } b.StopTimer() @@ -52,7 +55,10 @@ type structName struct { func BenchmarkMapFormName(b *testing.B) { var s structName for i := 0; i < b.N; i++ { - mapForm(&s, form) + err := mapForm(&s, form) + if err != nil { + b.Fatalf("Error on a form mapping") + } } b.StopTimer() diff --git a/gin.go b/gin.go index 894cf09..5863126 100644 --- a/gin.go +++ b/gin.go @@ -320,7 +320,10 @@ func (engine *Engine) RunUnix(file string) (err error) { return } defer listener.Close() - os.Chmod(file, 0777) + err = os.Chmod(file, 0777) + if err != nil { + return + } err = http.Serve(listener, engine) return } diff --git a/gin_integration_test.go b/gin_integration_test.go index 7e270b9..d86f610 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -90,7 +90,8 @@ func TestPusher(t *testing.T) { go func() { router.GET("/pusher", func(c *Context) { if pusher := c.Writer.Pusher(); pusher != nil { - pusher.Push("/assets/app.js", nil) + err := pusher.Push("/assets/app.js", nil) + assert.NoError(t, err) } c.String(http.StatusOK, "it worked") }) @@ -239,6 +240,7 @@ func TestBadListener(t *testing.T) { addr, err := net.ResolveTCPAddr("tcp", "localhost:10086") assert.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) + assert.NoError(t, err) listener.Close() assert.Error(t, router.RunListener(listener)) } diff --git a/render/render_test.go b/render/render_test.go index 95a01b6..376733d 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -347,7 +347,10 @@ func TestRenderRedirect(t *testing.T) { } w = httptest.NewRecorder() - assert.PanicsWithValue(t, "Cannot redirect with status code 200", func() { data2.Render(w) }) + assert.PanicsWithValue(t, "Cannot redirect with status code 200", func() { + err := data2.Render(w) + assert.NoError(t, err) + }) data3 := Redirect{ Code: http.StatusCreated, From 517eacb4f9ca7276511841c63e2911d6ec94c22a Mon Sep 17 00:00:00 2001 From: ishanray Date: Wed, 30 Oct 2019 23:13:39 -0400 Subject: [PATCH 408/912] Update gin.go (#2110) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 5863126..caf4cf2 100644 --- a/gin.go +++ b/gin.go @@ -30,7 +30,7 @@ type HandlerFunc func(*Context) // HandlersChain defines a HandlerFunc array. type HandlersChain []HandlerFunc -// Last returns the last handler in the chain. ie. the last handler is the main own. +// Last returns the last handler in the chain. ie. the last handler is the main one. func (c HandlersChain) Last() HandlerFunc { if length := len(c); length > 0 { return c[length-1] From aabaccbba2b670e3625c9d9e89b4157a47f052b8 Mon Sep 17 00:00:00 2001 From: Shamus Taylor Date: Thu, 31 Oct 2019 09:52:02 -0500 Subject: [PATCH 409/912] Close files opened in static file handler (#2118) * Close files opened in static file handler * Do not use defer --- routergroup.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index a1e6c92..2e7a5b9 100644 --- a/routergroup.go +++ b/routergroup.go @@ -193,13 +193,15 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS file := c.Param("filepath") // Check if file exists and/or if we have permission to access it - if _, err := fs.Open(file); err != nil { + f, err := fs.Open(file) + if err != nil { c.Writer.WriteHeader(http.StatusNotFound) c.handlers = group.engine.noRoute // Reset index c.index = -1 return } + f.Close() fileServer.ServeHTTP(c.Writer, c.Request) } From 0f951956d0b8b4b459a2f46bcd4e7118f0306210 Mon Sep 17 00:00:00 2001 From: linfangrong Date: Thu, 31 Oct 2019 23:17:12 +0800 Subject: [PATCH 410/912] [FIX] c.Request.FormFile maybe file, need close (#2114) --- context.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 18d328d..046f284 100644 --- a/context.go +++ b/context.go @@ -516,7 +516,11 @@ func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { return nil, err } } - _, fh, err := c.Request.FormFile(name) + f, fh, err := c.Request.FormFile(name) + if err != nil { + return nil, err + } + f.Close() return fh, err } From db9174ae0c2587fe1c755def0f88cb9aba9e9641 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 1 Nov 2019 03:47:40 +0100 Subject: [PATCH 411/912] fix ignore walking on form mapping (#1942) (#1943) --- binding/form_mapping.go | 7 ++++--- binding/form_mapping_test.go | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index ec78bfe..d6199c4 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -51,6 +51,10 @@ func mappingByPtr(ptr interface{}, setter setter, tag string) error { } func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { + if field.Tag.Get(tag) == "-" { // just ignoring this field + return false, nil + } + var vKind = value.Kind() if vKind == reflect.Ptr { @@ -112,9 +116,6 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter tagValue = field.Tag.Get(tag) tagValue, opts := head(tagValue, ",") - if tagValue == "-" { // just ignoring this field - return false, nil - } if tagValue == "" { // default value is FieldName tagValue = field.Name } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index c9d6111..2a56037 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -269,3 +269,13 @@ func TestMappingMapField(t *testing.T) { assert.NoError(t, err) assert.Equal(t, map[string]int{"one": 1}, s.M) } + +func TestMappingIgnoredCircularRef(t *testing.T) { + type S struct { + S *S `form:"-"` + } + var s S + + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) +} From 15ced05c5316609bce5b43389f8e3f06102a8b18 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 24 Nov 2019 10:25:21 +0800 Subject: [PATCH 412/912] ready to release v1.5.0 (#2109) * ready to release v1.5.0 * add some commit log * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * Update CHANGELOG.md Co-Authored-By: Dominik-K * remove refactor and update readme pr --- CHANGELOG.md | 40 +++++++++++++++++++++++++++++++++++++--- version.go | 2 +- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ccd2fa..0bb90f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ - -### Gin 1.4.0 +### Gin v1.5.0 + +- [FIX] Use DefaultWriter and DefaultErrorWriter for debug messages [#1891](https://github.com/gin-gonic/gin/pull/1891) +- [NEW] Now you can parse the inline lowercase start structure [#1893](https://github.com/gin-gonic/gin/pull/1893) +- [FIX] Some code improvements [#1909](https://github.com/gin-gonic/gin/pull/1909) +- [FIX] Use encode replace json marshal increase json encoder speed [#1546](https://github.com/gin-gonic/gin/pull/1546) +- [NEW] Hold matched route full path in the Context [#1826](https://github.com/gin-gonic/gin/pull/1826) +- [FIX] Fix context.Params race condition on Copy() [#1841](https://github.com/gin-gonic/gin/pull/1841) +- [NEW] Add context param query cache [#1450](https://github.com/gin-gonic/gin/pull/1450) +- [FIX] Improve GetQueryMap performance [#1918](https://github.com/gin-gonic/gin/pull/1918) +- [FIX] Improve get post data [#1920](https://github.com/gin-gonic/gin/pull/1920) +- [FIX] Use context instead of x/net/context [#1922](https://github.com/gin-gonic/gin/pull/1922) +- [FIX] Attempt to fix PostForm cache bug [#1931](https://github.com/gin-gonic/gin/pull/1931) +- [NEW] Add support of multipart multi files [#1949](https://github.com/gin-gonic/gin/pull/1949) +- [NEW] Support bind http header param [#1957](https://github.com/gin-gonic/gin/pull/1957) +- [FIX] Drop support for go1.8 and go1.9 [#1933](https://github.com/gin-gonic/gin/pull/1933) +- [FIX] Bugfix for the FullPath feature [#1919](https://github.com/gin-gonic/gin/pull/1919) +- [FIX] Gin1.5 bytes.Buffer to strings.Builder [#1939](https://github.com/gin-gonic/gin/pull/1939) +- [FIX] Upgrade github.com/ugorji/go/codec [#1969](https://github.com/gin-gonic/gin/pull/1969) +- [NEW] Support bind unix time [#1980](https://github.com/gin-gonic/gin/pull/1980) +- [FIX] Simplify code [#2004](https://github.com/gin-gonic/gin/pull/2004) +- [NEW] Support negative Content-Length in DataFromReader [#1981](https://github.com/gin-gonic/gin/pull/1981) +- [FIX] Identify terminal on a RISC-V architecture for auto-colored logs [#2019](https://github.com/gin-gonic/gin/pull/2019) +- [BREAKING] `Context.JSONP()` now expects a semicolon (`;`) at the end [#2007](https://github.com/gin-gonic/gin/pull/2007) +- [BREAKING] Upgrade default `binding.Validator` to v9 (see [its changelog](https://github.com/go-playground/validator/releases/tag/v9.0.0)) [#1015](https://github.com/gin-gonic/gin/pull/1015) +- [NEW] Add `DisallowUnknownFields()` in `Context.BindJSON()` [#2028](https://github.com/gin-gonic/gin/pull/2028) +- [NEW] Use specific `net.Listener` with `Engine.RunListener()` [#2023](https://github.com/gin-gonic/gin/pull/2023) +- [FIX] Fix some typo [#2079](https://github.com/gin-gonic/gin/pull/2079) [#2080](https://github.com/gin-gonic/gin/pull/2080) +- [FIX] Relocate binding body tests [#2086](https://github.com/gin-gonic/gin/pull/2086) +- [FIX] Use Writer in Context.Status [#1606](https://github.com/gin-gonic/gin/pull/1606) +- [FIX] `Engine.RunUnix()` now returns the error if it can't change the file mode [#2093](https://github.com/gin-gonic/gin/pull/2093) +- [FIX] `RouterGroup.StaticFS()` leaked files. Now it closes them. [#2118](https://github.com/gin-gonic/gin/pull/2118) +- [FIX] `Context.Request.FormFile` leaked file. Now it closes it. [#2114](https://github.com/gin-gonic/gin/pull/2114) +- [FIX] Ignore walking on `form:"-"` mapping [#1943](https://github.com/gin-gonic/gin/pull/1943) + +### Gin v1.4.0 - [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569) - [NEW] Refactor of form mapping multipart request [#1829](https://github.com/gin-gonic/gin/pull/1829) @@ -56,7 +90,7 @@ - [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491) -### Gin 1.3.0 +### Gin v1.3.0 - [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383) - [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358) diff --git a/version.go b/version.go index 028caeb..6f8235f 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.4.0-dev" +const Version = "v1.5.0" From 70ca31bc113523fa1e1309c01d5b3249ddfdab23 Mon Sep 17 00:00:00 2001 From: Ivan Chen Date: Sun, 24 Nov 2019 16:22:18 +0800 Subject: [PATCH 413/912] fix comment in `mode.go` (#2129) EnableJsonDisallowUnknownFields => EnableJsonDecoderDisallowUnknownFields --- mode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode.go b/mode.go index c3c37fd..edfc294 100644 --- a/mode.go +++ b/mode.go @@ -77,7 +77,7 @@ func EnableJsonDecoderUseNumber() { binding.EnableDecoderUseNumber = true } -// EnableJsonDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to +// EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to // call the DisallowUnknownFields method on the JSON Decoder instance. func EnableJsonDecoderDisallowUnknownFields() { binding.EnableDecoderDisallowUnknownFields = true From 2ee0e963942d91be7944dfcc07dc8c02a4a78566 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 24 Nov 2019 23:07:56 +0800 Subject: [PATCH 414/912] Drop support go1.10 (#2147) --- .travis.yml | 1 - README.md | 2 +- debug.go | 2 +- debug_test.go | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 748a07a..4a4ab81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: go matrix: fast_finish: true include: - - go: 1.10.x - go: 1.11.x env: GO111MODULE=on - go: 1.12.x diff --git a/README.md b/README.md index 3dd99d9..8aa5050 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. The first need [Go](https://golang.org/) installed (**version 1.10+ is required**), then you can use the below Go command to install Gin. +1. The first need [Go](https://golang.org/) installed (**version 1.11+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin diff --git a/debug.go b/debug.go index 49080db..c66ca44 100644 --- a/debug.go +++ b/debug.go @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon. + debugPrint(`[WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon. `) } diff --git a/debug_test.go b/debug_test.go index d6f320e..d707b4b 100644 --- a/debug_test.go +++ b/debug_test.go @@ -91,7 +91,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } From b52a1a1588f8af4e6d1e2a711adbae42d11bb59d Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Mon, 25 Nov 2019 03:45:53 +0100 Subject: [PATCH 415/912] allow empty headers on DataFromReader (#2121) --- context_test.go | 17 +++++++++++++++++ render/reader.go | 3 +++ render/reader_test.go | 23 +++++++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 render/reader_test.go diff --git a/context_test.go b/context_test.go index f7bb0f5..18709d3 100644 --- a/context_test.go +++ b/context_test.go @@ -1799,6 +1799,23 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, extraHeaders["Content-Disposition"], w.Header().Get("Content-Disposition")) } +func TestContextRenderDataFromReaderNoHeaders(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + body := "#!PNG some raw data" + reader := strings.NewReader(body) + contentLength := int64(len(body)) + contentType := "image/png" + + c.DataFromReader(http.StatusOK, contentLength, contentType, reader, nil) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, body, w.Body.String()) + assert.Equal(t, contentType, w.Header().Get("Content-Type")) + assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length")) +} + type TestResponseRecorder struct { *httptest.ResponseRecorder closeChannel chan bool diff --git a/render/reader.go b/render/reader.go index 502d939..d5282e4 100644 --- a/render/reader.go +++ b/render/reader.go @@ -22,6 +22,9 @@ type Reader struct { func (r Reader) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) if r.ContentLength >= 0 { + if r.Headers == nil { + r.Headers = map[string]string{} + } r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) } r.writeHeaders(w, r.Headers) diff --git a/render/reader_test.go b/render/reader_test.go new file mode 100644 index 0000000..3930f51 --- /dev/null +++ b/render/reader_test.go @@ -0,0 +1,23 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package render + +import ( + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestReaderRenderNoHeaders(t *testing.T) { + content := "test" + r := Reader{ + ContentLength: int64(len(content)), + Reader: strings.NewReader(content), + } + err := r.Render(httptest.NewRecorder()) + require.NoError(t, err) +} From 3737520f17457b8a06a35f612607cb4799e53a67 Mon Sep 17 00:00:00 2001 From: BradyBromley <51128276+BradyBromley@users.noreply.github.com> Date: Sun, 24 Nov 2019 19:03:36 -0800 Subject: [PATCH 416/912] Changed wording for clarity in README.md (#2122) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8aa5050..3f2d3c2 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) -Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. +Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. ## Contents From e90e2ba9b369057e4ee419a06486bae83313cf54 Mon Sep 17 00:00:00 2001 From: Xudong Cai Date: Mon, 25 Nov 2019 14:49:45 +0800 Subject: [PATCH 417/912] upgrade go-validator to v10 (#2149) * upgrade go-validator to v10 * fix fmt --- binding/default_validator.go | 2 +- binding/validate_test.go | 2 +- go.mod | 6 +----- go.sum | 22 ++++++++++++---------- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index 50e0d57..a4c1a7f 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -8,7 +8,7 @@ import ( "reflect" "sync" - "gopkg.in/go-playground/validator.v9" + "github.com/go-playground/validator/v10" ) type defaultValidator struct { diff --git a/binding/validate_test.go b/binding/validate_test.go index 81f7883..5299fbf 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" + "github.com/go-playground/validator/v10" "github.com/stretchr/testify/assert" - "gopkg.in/go-playground/validator.v9" ) type testInterface interface { diff --git a/go.mod b/go.mod index 3415185..1213bd2 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,11 @@ go 1.12 require ( github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/locales v0.12.1 // indirect - github.com/go-playground/universal-translator v0.16.0 // indirect + github.com/go-playground/validator/v10 v10.0.1 github.com/golang/protobuf v1.3.2 github.com/json-iterator/go v1.1.7 - github.com/leodido/go-urn v1.1.0 // indirect github.com/mattn/go-isatty v0.0.9 github.com/stretchr/testify v1.4.0 github.com/ugorji/go/codec v1.1.7 - gopkg.in/go-playground/assert.v1 v1.2.1 // indirect - gopkg.in/go-playground/validator.v9 v9.29.1 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 129ad38..9815f2f 100644 --- a/go.sum +++ b/go.sum @@ -3,17 +3,21 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= -github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.0.1 h1:QgDDZpXlR/L3atIL2PbFt0TpazbtN7N6PxTGcgcyEUg= +github.com/go-playground/validator/v10 v10.0.1/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= @@ -32,11 +36,9 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 3c8e29b53c6b8334cc270d8e478c26aa4a1ce4b7 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 25 Nov 2019 15:42:23 +0800 Subject: [PATCH 418/912] drop support govendor (#2148) --- .travis.yml | 2 +- Makefile | 16 +---- README.md | 38 ----------- vendor/vendor.json | 153 --------------------------------------------- 4 files changed, 4 insertions(+), 205 deletions(-) delete mode 100644 vendor/vendor.json diff --git a/.travis.yml b/.travis.yml index 4a4ab81..b80b257 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: - if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi install: - - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; else make install; fi + - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi diff --git a/Makefile b/Makefile index 51a6b91..e69dbd8 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,10 @@ GO ?= go GOFMT ?= gofmt "-s" -PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/) -VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/ | grep -v /examples/) -GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") +PACKAGES ?= $(shell $(GO) list ./...) +VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/) +GOFILES := $(shell find . -name "*.go") TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples) -all: install - -install: deps - govendor sync - .PHONY: test test: echo "mode: count" > coverage.out @@ -48,11 +43,6 @@ fmt-check: vet: $(GO) vet $(VETPACKAGES) -deps: - @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/kardianos/govendor; \ - fi - .PHONY: lint lint: @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ diff --git a/README.md b/README.md index 3f2d3c2..012152a 100644 --- a/README.md +++ b/README.md @@ -88,44 +88,6 @@ import "github.com/gin-gonic/gin" import "net/http" ``` -### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) - -1. `go get` govendor - -```sh -$ go get github.com/kardianos/govendor -``` -2. Create your project folder and `cd` inside - -```sh -$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" -``` - -If you are on a Mac and you're installing Go 1.8 (released: Feb 2017) or later, GOPATH is automatically determined by the Go toolchain for you. It defaults to $HOME/go on macOS so you can create your project like this - -```sh -$ mkdir -p $HOME/go/src/github.com/myusername/project && cd "$_" -``` - -3. Vendor init your project and add gin - -```sh -$ govendor init -$ govendor fetch github.com/gin-gonic/gin@v1.3 -``` - -4. Copy a starting template inside your project - -```sh -$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go -``` - -5. Run your project - -```sh -$ go run main.go -``` - ## Quick start ```sh diff --git a/vendor/vendor.json b/vendor/vendor.json deleted file mode 100644 index 70b2d9e..0000000 --- a/vendor/vendor.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "comment": "v1.4.0", - "ignore": "test", - "package": [ - { - "checksumSHA1": "CSPbwbyzqA6sfORicn4HFtIhF/c=", - "path": "github.com/davecgh/go-spew/spew", - "revision": "8991bc29aa16c548c550c7ff78260e27b9ab7c73", - "revisionTime": "2018-02-21T22:46:20Z", - "version": "v1.1", - "versionExact": "v1.1.1" - }, - { - "checksumSHA1": "qlEzrgKgIkh7y0ePm9BNo1cNdXo=", - "path": "github.com/gin-contrib/sse", - "revision": "54d8467d122d380a14768b6b4e5cd7ca4755938f", - "revisionTime": "2019-06-02T15:02:53Z", - "version": "v0.1", - "versionExact": "v0.1.0" - }, - { - "checksumSHA1": "b4DmyMT9bicTRVJw1hJXHLhIH+0=", - "path": "github.com/go-playground/locales", - "revision": "f63010822830b6fe52288ee52d5a1151088ce039", - "revisionTime": "2018-03-23T16:04:04Z", - "version": "v0.12", - "versionExact": "v0.12.1" - }, - { - "checksumSHA1": "JgF260rC9YpWyY5WEljjimWLUXs=", - "path": "github.com/go-playground/locales/currency", - "revision": "630ebbb602847eba93e75ae38bbc7bb7abcf1ff3", - "revisionTime": "2019-04-30T15:33:29Z" - }, - { - "checksumSHA1": "9pKcUHBaVS+360X6h4IowhmOPjk=", - "path": "github.com/go-playground/universal-translator", - "revision": "b32fa301c9fe55953584134cb6853a13c87ec0a1", - "revisionTime": "2017-02-09T16:11:52Z", - "version": "v0.16", - "versionExact": "v0.16.0" - }, - { - "checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=", - "path": "github.com/golang/protobuf/proto", - "revision": "c823c79ea1570fb5ff454033735a8e68575d1d0f", - "revisionTime": "2019-02-05T22:20:52Z", - "version": "v1.3", - "versionExact": "v1.3.0" - }, - { - "checksumSHA1": "zNo6yGy/bCJuzkEcP70oEBtOB2M=", - "path": "github.com/leodido/go-urn", - "revision": "70078a794e8ea4b497ba7c19a78cd60f90ccf0f4", - "revisionTime": "2018-05-24T03:26:21Z", - "version": "v1.1", - "versionExact": "v1.1.0" - }, - { - "checksumSHA1": "TB2vxux9xQbvsTHOVt4aRTuvSn4=", - "path": "github.com/json-iterator/go", - "revision": "0ff49de124c6f76f8494e194af75bde0f1a49a29", - "revisionTime": "2019-03-06T14:29:09Z", - "version": "v1.1", - "versionExact": "v1.1.6" - }, - { - "checksumSHA1": "Ya+baVBU/RkXXUWD3LGFmGJiiIg=", - "path": "github.com/mattn/go-isatty", - "revision": "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7", - "revisionTime": "2019-03-12T13:58:54Z", - "version": "v0.0", - "versionExact": "v0.0.7" - }, - { - "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", - "path": "github.com/modern-go/concurrent", - "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", - "revisionTime": "2018-03-06T01:26:44Z" - }, - { - "checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=", - "path": "github.com/modern-go/reflect2", - "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", - "revisionTime": "2018-07-18T01:23:57Z" - }, - { - "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", - "path": "github.com/pmezard/go-difflib/difflib", - "revision": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc", - "revisionTime": "2018-12-26T10:54:42Z" - }, - { - "checksumSHA1": "cpNsoLqBprpKh+VZTBOZNVXzBEk=", - "path": "github.com/stretchr/objx", - "revision": "c61a9dfcced1815e7d40e214d00d1a8669a9f58c", - "revisionTime": "2019-02-11T16:23:28Z" - }, - { - "checksumSHA1": "DBdcVxnvaINHhWyyGgih/Mel6gE=", - "path": "github.com/stretchr/testify", - "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", - "revisionTime": "2018-12-05T02:12:43Z", - "version": "v1.3", - "versionExact": "v1.3.0" - }, - { - "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", - "path": "github.com/stretchr/testify/assert", - "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", - "revisionTime": "2018-05-06T18:05:49Z", - "version": "v1.2", - "versionExact": "v1.2.2" - }, - { - "checksumSHA1": "wnEANt4k5X/KGwoFyfSSnpxULm4=", - "path": "github.com/stretchr/testify/require", - "revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686", - "revisionTime": "2018-05-06T18:05:49Z" - }, - { - "checksumSHA1": "S4ei9eSqVThDio0Jn2sav6yUbvg=", - "path": "github.com/ugorji/go/codec", - "revision": "82dbfaf494e3b01d2d481376f11f6a5c8cf9599f", - "revisionTime": "2019-07-02T14:15:27Z", - "version": "v1.1", - "versionExact": "v1.1.6" - }, - { - "checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=", - "path": "golang.org/x/sys/unix", - "revision": "a43fa875dd822b81eb6d2ad538bc1f4caba169bd", - "revisionTime": "2019-05-02T15:41:39Z" - }, - { - "checksumSHA1": "ACzc7AkwLtNgKhqtj8V7SGUJgnw=", - "path": "gopkg.in/go-playground/validator.v9", - "revision": "46b4b1e301c24cac870ffcb4ba5c8a703d1ef475", - "revisionTime": "2019-03-31T13:31:25Z", - "version": "v9.28", - "versionExact": "v9.28.0" - }, - { - "checksumSHA1": "QqDq2x8XOU7IoOR98Cx1eiV5QY8=", - "path": "gopkg.in/yaml.v2", - "revision": "51d6538a90f86fe93ac480b35f37b2be17fef232", - "revisionTime": "2018-11-15T11:05:04Z", - "version": "v2.2", - "versionExact": "v2.2.2" - } - ], - "rootPath": "github.com/gin-gonic/gin" -} From 231ff00d1f77d30f8dd97278680d7731e1317d55 Mon Sep 17 00:00:00 2001 From: Ngalim Siregar Date: Tue, 26 Nov 2019 07:19:30 +0700 Subject: [PATCH 419/912] Refactor redirect request in gin.go (#1970) * Refactor redirect request in gin.go * Update http status code --- binding/form.go | 2 +- gin.go | 31 ++++++++++++++++--------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/binding/form.go b/binding/form.go index 9e9fc3d..b93c34c 100644 --- a/binding/form.go +++ b/binding/form.go @@ -8,7 +8,7 @@ import ( "net/http" ) -const defaultMemory = 32 * 1024 * 1024 +const defaultMemory = 32 << 20 type formBinding struct{} type formPostBinding struct{} diff --git a/gin.go b/gin.go index caf4cf2..c194d6b 100644 --- a/gin.go +++ b/gin.go @@ -457,18 +457,11 @@ func redirectTrailingSlash(c *Context) { if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { p = prefix + "/" + req.URL.Path } - code := http.StatusMovedPermanently // Permanent redirect, request with GET method - if req.Method != "GET" { - code = http.StatusTemporaryRedirect - } - req.URL.Path = p + "/" if length := len(p); length > 1 && p[length-1] == '/' { req.URL.Path = p[:length-1] } - debugPrint("redirecting request %d: %s --> %s", code, p, req.URL.String()) - http.Redirect(c.Writer, req, req.URL.String(), code) - c.writermem.WriteHeaderNow() + redirectRequest(c) } func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { @@ -476,15 +469,23 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { rPath := req.URL.Path if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok { - code := http.StatusMovedPermanently // Permanent redirect, request with GET method - if req.Method != "GET" { - code = http.StatusTemporaryRedirect - } req.URL.Path = string(fixedPath) - debugPrint("redirecting request %d: %s --> %s", code, rPath, req.URL.String()) - http.Redirect(c.Writer, req, req.URL.String(), code) - c.writermem.WriteHeaderNow() + redirectRequest(c) return true } return false } + +func redirectRequest(c *Context) { + req := c.Request + rPath := req.URL.Path + rURL := req.URL.String() + + code := http.StatusMovedPermanently // Permanent redirect, request with GET method + if req.Method != "GET" { + code = http.StatusTemporaryRedirect + } + debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL) + http.Redirect(c.Writer, req, rURL, code) + c.writermem.WriteHeaderNow() +} From 352d69c71f45d51971f2d23f5c4450c6410aa481 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 29 Nov 2019 00:02:02 +0800 Subject: [PATCH 420/912] =?UTF-8?q?chore(performance):=20Improve=20perform?= =?UTF-8?q?ance=20for=20adding=20RemoveExtraS=E2=80=A6=20(#2159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Add RemoveExtraSlash flag * fix testing Signed-off-by: Bo-Yi Wu --- gin.go | 10 +++++++++- routes_test.go | 40 ++++++++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/gin.go b/gin.go index c194d6b..f0cd09e 100644 --- a/gin.go +++ b/gin.go @@ -97,6 +97,10 @@ type Engine struct { // method call. MaxMultipartMemory int64 + // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. + // See the PR #1817 and issue #1644 + RemoveExtraSlash bool + delims render.Delims secureJsonPrefix string HTMLRender render.HTMLRender @@ -134,6 +138,7 @@ func New() *Engine { ForwardedByClientIP: true, AppEngine: defaultAppEngine, UseRawPath: false, + RemoveExtraSlash: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), @@ -385,7 +390,10 @@ func (engine *Engine) handleHTTPRequest(c *Context) { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } - rPath = cleanPath(rPath) + + if engine.RemoveExtraSlash { + rPath = cleanPath(rPath) + } // Find root of the tree for the given HTTP method t := engine.trees diff --git a/routes_test.go b/routes_test.go index 0c2f9a0..d1ddbe9 100644 --- a/routes_test.go +++ b/routes_test.go @@ -263,6 +263,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { lastName := "" wild := "" router := New() + router.RemoveExtraSlash = true router.GET("/test/:name/:last_name/*wild", func(c *Context) { name = c.Params.ByName("name") lastName = c.Params.ByName("last_name") @@ -407,6 +408,29 @@ func TestRouteNotAllowedDisabled(t *testing.T) { assert.Equal(t, http.StatusNotFound, w.Code) } +func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) { + router := New() + router.RemoveExtraSlash = true + router.GET("/path", func(c *Context) {}) + router.GET("/", func(c *Context) {}) + + testRoutes := []struct { + route string + code int + location string + }{ + {"/../path", http.StatusOK, ""}, // CleanPath + {"/nope", http.StatusNotFound, ""}, // NotFound + } + for _, tr := range testRoutes { + w := performRequest(router, "GET", tr.route) + assert.Equal(t, tr.code, w.Code) + if w.Code != http.StatusNotFound { + assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) + } + } +} + func TestRouterNotFound(t *testing.T) { router := New() router.RedirectFixedPath = true @@ -419,14 +443,14 @@ func TestRouterNotFound(t *testing.T) { code int location string }{ - {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ - {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ - {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case - {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case - {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ - {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ - {"/../path", http.StatusOK, ""}, // CleanPath - {"/nope", http.StatusNotFound, ""}, // NotFound + {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ + {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ + {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case + {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case + {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ + {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ + {"/../path", http.StatusMovedPermanently, "/path"}, // Without CleanPath + {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) From d5f12ac6d7ba14b9bc85fa7d0ae486289d52bdf1 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Fri, 29 Nov 2019 07:50:49 +0800 Subject: [PATCH 421/912] use http method constant (#2155) * use http method constant * fix typo --- binding/binding.go | 2 +- gin.go | 2 +- gin_integration_test.go | 2 +- githubapi_test.go | 498 ++++++++++++++++++++-------------------- logger.go | 14 +- routergroup.go | 32 +-- routergroup_test.go | 32 +-- routes_test.go | 156 ++++++------- 8 files changed, 369 insertions(+), 369 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 6d58c3c..f578aa5 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -84,7 +84,7 @@ var ( // Default returns the appropriate Binding instance based on the HTTP method // and the content type. func Default(method, contentType string) Binding { - if method == "GET" { + if method == http.MethodGet { return Form } diff --git a/gin.go b/gin.go index f0cd09e..71f3fd5 100644 --- a/gin.go +++ b/gin.go @@ -490,7 +490,7 @@ func redirectRequest(c *Context) { rURL := req.URL.String() code := http.StatusMovedPermanently // Permanent redirect, request with GET method - if req.Method != "GET" { + if req.Method != http.MethodGet { code = http.StatusTemporaryRedirect } debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL) diff --git a/gin_integration_test.go b/gin_integration_test.go index d86f610..f29d1fc 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -291,7 +291,7 @@ func TestConcurrentHandleContext(t *testing.T) { // } func testGetRequestHandler(t *testing.T, h http.Handler, url string) { - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequest(http.MethodGet, url, nil) assert.NoError(t, err) w := httptest.NewRecorder() diff --git a/githubapi_test.go b/githubapi_test.go index fb74d65..925c5a1 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -24,265 +24,265 @@ type route struct { // http://developer.github.com/v3/ var githubAPI = []route{ // OAuth Authorizations - {"GET", "/authorizations"}, - {"GET", "/authorizations/:id"}, - {"POST", "/authorizations"}, - //{"PUT", "/authorizations/clients/:client_id"}, - //{"PATCH", "/authorizations/:id"}, - {"DELETE", "/authorizations/:id"}, - {"GET", "/applications/:client_id/tokens/:access_token"}, - {"DELETE", "/applications/:client_id/tokens"}, - {"DELETE", "/applications/:client_id/tokens/:access_token"}, + {http.MethodGet, "/authorizations"}, + {http.MethodGet, "/authorizations/:id"}, + {http.MethodPost, "/authorizations"}, + //{http.MethodPut, "/authorizations/clients/:client_id"}, + //{http.MethodPatch, "/authorizations/:id"}, + {http.MethodDelete, "/authorizations/:id"}, + {http.MethodGet, "/applications/:client_id/tokens/:access_token"}, + {http.MethodDelete, "/applications/:client_id/tokens"}, + {http.MethodDelete, "/applications/:client_id/tokens/:access_token"}, // Activity - {"GET", "/events"}, - {"GET", "/repos/:owner/:repo/events"}, - {"GET", "/networks/:owner/:repo/events"}, - {"GET", "/orgs/:org/events"}, - {"GET", "/users/:user/received_events"}, - {"GET", "/users/:user/received_events/public"}, - {"GET", "/users/:user/events"}, - {"GET", "/users/:user/events/public"}, - {"GET", "/users/:user/events/orgs/:org"}, - {"GET", "/feeds"}, - {"GET", "/notifications"}, - {"GET", "/repos/:owner/:repo/notifications"}, - {"PUT", "/notifications"}, - {"PUT", "/repos/:owner/:repo/notifications"}, - {"GET", "/notifications/threads/:id"}, - //{"PATCH", "/notifications/threads/:id"}, - {"GET", "/notifications/threads/:id/subscription"}, - {"PUT", "/notifications/threads/:id/subscription"}, - {"DELETE", "/notifications/threads/:id/subscription"}, - {"GET", "/repos/:owner/:repo/stargazers"}, - {"GET", "/users/:user/starred"}, - {"GET", "/user/starred"}, - {"GET", "/user/starred/:owner/:repo"}, - {"PUT", "/user/starred/:owner/:repo"}, - {"DELETE", "/user/starred/:owner/:repo"}, - {"GET", "/repos/:owner/:repo/subscribers"}, - {"GET", "/users/:user/subscriptions"}, - {"GET", "/user/subscriptions"}, - {"GET", "/repos/:owner/:repo/subscription"}, - {"PUT", "/repos/:owner/:repo/subscription"}, - {"DELETE", "/repos/:owner/:repo/subscription"}, - {"GET", "/user/subscriptions/:owner/:repo"}, - {"PUT", "/user/subscriptions/:owner/:repo"}, - {"DELETE", "/user/subscriptions/:owner/:repo"}, + {http.MethodGet, "/events"}, + {http.MethodGet, "/repos/:owner/:repo/events"}, + {http.MethodGet, "/networks/:owner/:repo/events"}, + {http.MethodGet, "/orgs/:org/events"}, + {http.MethodGet, "/users/:user/received_events"}, + {http.MethodGet, "/users/:user/received_events/public"}, + {http.MethodGet, "/users/:user/events"}, + {http.MethodGet, "/users/:user/events/public"}, + {http.MethodGet, "/users/:user/events/orgs/:org"}, + {http.MethodGet, "/feeds"}, + {http.MethodGet, "/notifications"}, + {http.MethodGet, "/repos/:owner/:repo/notifications"}, + {http.MethodPut, "/notifications"}, + {http.MethodPut, "/repos/:owner/:repo/notifications"}, + {http.MethodGet, "/notifications/threads/:id"}, + //{http.MethodPatch, "/notifications/threads/:id"}, + {http.MethodGet, "/notifications/threads/:id/subscription"}, + {http.MethodPut, "/notifications/threads/:id/subscription"}, + {http.MethodDelete, "/notifications/threads/:id/subscription"}, + {http.MethodGet, "/repos/:owner/:repo/stargazers"}, + {http.MethodGet, "/users/:user/starred"}, + {http.MethodGet, "/user/starred"}, + {http.MethodGet, "/user/starred/:owner/:repo"}, + {http.MethodPut, "/user/starred/:owner/:repo"}, + {http.MethodDelete, "/user/starred/:owner/:repo"}, + {http.MethodGet, "/repos/:owner/:repo/subscribers"}, + {http.MethodGet, "/users/:user/subscriptions"}, + {http.MethodGet, "/user/subscriptions"}, + {http.MethodGet, "/repos/:owner/:repo/subscription"}, + {http.MethodPut, "/repos/:owner/:repo/subscription"}, + {http.MethodDelete, "/repos/:owner/:repo/subscription"}, + {http.MethodGet, "/user/subscriptions/:owner/:repo"}, + {http.MethodPut, "/user/subscriptions/:owner/:repo"}, + {http.MethodDelete, "/user/subscriptions/:owner/:repo"}, // Gists - {"GET", "/users/:user/gists"}, - {"GET", "/gists"}, - //{"GET", "/gists/public"}, - //{"GET", "/gists/starred"}, - {"GET", "/gists/:id"}, - {"POST", "/gists"}, - //{"PATCH", "/gists/:id"}, - {"PUT", "/gists/:id/star"}, - {"DELETE", "/gists/:id/star"}, - {"GET", "/gists/:id/star"}, - {"POST", "/gists/:id/forks"}, - {"DELETE", "/gists/:id"}, + {http.MethodGet, "/users/:user/gists"}, + {http.MethodGet, "/gists"}, + //{http.MethodGet, "/gists/public"}, + //{http.MethodGet, "/gists/starred"}, + {http.MethodGet, "/gists/:id"}, + {http.MethodPost, "/gists"}, + //{http.MethodPatch, "/gists/:id"}, + {http.MethodPut, "/gists/:id/star"}, + {http.MethodDelete, "/gists/:id/star"}, + {http.MethodGet, "/gists/:id/star"}, + {http.MethodPost, "/gists/:id/forks"}, + {http.MethodDelete, "/gists/:id"}, // Git Data - {"GET", "/repos/:owner/:repo/git/blobs/:sha"}, - {"POST", "/repos/:owner/:repo/git/blobs"}, - {"GET", "/repos/:owner/:repo/git/commits/:sha"}, - {"POST", "/repos/:owner/:repo/git/commits"}, - //{"GET", "/repos/:owner/:repo/git/refs/*ref"}, - {"GET", "/repos/:owner/:repo/git/refs"}, - {"POST", "/repos/:owner/:repo/git/refs"}, - //{"PATCH", "/repos/:owner/:repo/git/refs/*ref"}, - //{"DELETE", "/repos/:owner/:repo/git/refs/*ref"}, - {"GET", "/repos/:owner/:repo/git/tags/:sha"}, - {"POST", "/repos/:owner/:repo/git/tags"}, - {"GET", "/repos/:owner/:repo/git/trees/:sha"}, - {"POST", "/repos/:owner/:repo/git/trees"}, + {http.MethodGet, "/repos/:owner/:repo/git/blobs/:sha"}, + {http.MethodPost, "/repos/:owner/:repo/git/blobs"}, + {http.MethodGet, "/repos/:owner/:repo/git/commits/:sha"}, + {http.MethodPost, "/repos/:owner/:repo/git/commits"}, + //{http.MethodGet, "/repos/:owner/:repo/git/refs/*ref"}, + {http.MethodGet, "/repos/:owner/:repo/git/refs"}, + {http.MethodPost, "/repos/:owner/:repo/git/refs"}, + //{http.MethodPatch, "/repos/:owner/:repo/git/refs/*ref"}, + //{http.MethodDelete, "/repos/:owner/:repo/git/refs/*ref"}, + {http.MethodGet, "/repos/:owner/:repo/git/tags/:sha"}, + {http.MethodPost, "/repos/:owner/:repo/git/tags"}, + {http.MethodGet, "/repos/:owner/:repo/git/trees/:sha"}, + {http.MethodPost, "/repos/:owner/:repo/git/trees"}, // Issues - {"GET", "/issues"}, - {"GET", "/user/issues"}, - {"GET", "/orgs/:org/issues"}, - {"GET", "/repos/:owner/:repo/issues"}, - {"GET", "/repos/:owner/:repo/issues/:number"}, - {"POST", "/repos/:owner/:repo/issues"}, - //{"PATCH", "/repos/:owner/:repo/issues/:number"}, - {"GET", "/repos/:owner/:repo/assignees"}, - {"GET", "/repos/:owner/:repo/assignees/:assignee"}, - {"GET", "/repos/:owner/:repo/issues/:number/comments"}, - //{"GET", "/repos/:owner/:repo/issues/comments"}, - //{"GET", "/repos/:owner/:repo/issues/comments/:id"}, - {"POST", "/repos/:owner/:repo/issues/:number/comments"}, - //{"PATCH", "/repos/:owner/:repo/issues/comments/:id"}, - //{"DELETE", "/repos/:owner/:repo/issues/comments/:id"}, - {"GET", "/repos/:owner/:repo/issues/:number/events"}, - //{"GET", "/repos/:owner/:repo/issues/events"}, - //{"GET", "/repos/:owner/:repo/issues/events/:id"}, - {"GET", "/repos/:owner/:repo/labels"}, - {"GET", "/repos/:owner/:repo/labels/:name"}, - {"POST", "/repos/:owner/:repo/labels"}, - //{"PATCH", "/repos/:owner/:repo/labels/:name"}, - {"DELETE", "/repos/:owner/:repo/labels/:name"}, - {"GET", "/repos/:owner/:repo/issues/:number/labels"}, - {"POST", "/repos/:owner/:repo/issues/:number/labels"}, - {"DELETE", "/repos/:owner/:repo/issues/:number/labels/:name"}, - {"PUT", "/repos/:owner/:repo/issues/:number/labels"}, - {"DELETE", "/repos/:owner/:repo/issues/:number/labels"}, - {"GET", "/repos/:owner/:repo/milestones/:number/labels"}, - {"GET", "/repos/:owner/:repo/milestones"}, - {"GET", "/repos/:owner/:repo/milestones/:number"}, - {"POST", "/repos/:owner/:repo/milestones"}, - //{"PATCH", "/repos/:owner/:repo/milestones/:number"}, - {"DELETE", "/repos/:owner/:repo/milestones/:number"}, + {http.MethodGet, "/issues"}, + {http.MethodGet, "/user/issues"}, + {http.MethodGet, "/orgs/:org/issues"}, + {http.MethodGet, "/repos/:owner/:repo/issues"}, + {http.MethodGet, "/repos/:owner/:repo/issues/:number"}, + {http.MethodPost, "/repos/:owner/:repo/issues"}, + //{http.MethodPatch, "/repos/:owner/:repo/issues/:number"}, + {http.MethodGet, "/repos/:owner/:repo/assignees"}, + {http.MethodGet, "/repos/:owner/:repo/assignees/:assignee"}, + {http.MethodGet, "/repos/:owner/:repo/issues/:number/comments"}, + //{http.MethodGet, "/repos/:owner/:repo/issues/comments"}, + //{http.MethodGet, "/repos/:owner/:repo/issues/comments/:id"}, + {http.MethodPost, "/repos/:owner/:repo/issues/:number/comments"}, + //{http.MethodPatch, "/repos/:owner/:repo/issues/comments/:id"}, + //{http.MethodDelete, "/repos/:owner/:repo/issues/comments/:id"}, + {http.MethodGet, "/repos/:owner/:repo/issues/:number/events"}, + //{http.MethodGet, "/repos/:owner/:repo/issues/events"}, + //{http.MethodGet, "/repos/:owner/:repo/issues/events/:id"}, + {http.MethodGet, "/repos/:owner/:repo/labels"}, + {http.MethodGet, "/repos/:owner/:repo/labels/:name"}, + {http.MethodPost, "/repos/:owner/:repo/labels"}, + //{http.MethodPatch, "/repos/:owner/:repo/labels/:name"}, + {http.MethodDelete, "/repos/:owner/:repo/labels/:name"}, + {http.MethodGet, "/repos/:owner/:repo/issues/:number/labels"}, + {http.MethodPost, "/repos/:owner/:repo/issues/:number/labels"}, + {http.MethodDelete, "/repos/:owner/:repo/issues/:number/labels/:name"}, + {http.MethodPut, "/repos/:owner/:repo/issues/:number/labels"}, + {http.MethodDelete, "/repos/:owner/:repo/issues/:number/labels"}, + {http.MethodGet, "/repos/:owner/:repo/milestones/:number/labels"}, + {http.MethodGet, "/repos/:owner/:repo/milestones"}, + {http.MethodGet, "/repos/:owner/:repo/milestones/:number"}, + {http.MethodPost, "/repos/:owner/:repo/milestones"}, + //{http.MethodPatch, "/repos/:owner/:repo/milestones/:number"}, + {http.MethodDelete, "/repos/:owner/:repo/milestones/:number"}, // Miscellaneous - {"GET", "/emojis"}, - {"GET", "/gitignore/templates"}, - {"GET", "/gitignore/templates/:name"}, - {"POST", "/markdown"}, - {"POST", "/markdown/raw"}, - {"GET", "/meta"}, - {"GET", "/rate_limit"}, + {http.MethodGet, "/emojis"}, + {http.MethodGet, "/gitignore/templates"}, + {http.MethodGet, "/gitignore/templates/:name"}, + {http.MethodPost, "/markdown"}, + {http.MethodPost, "/markdown/raw"}, + {http.MethodGet, "/meta"}, + {http.MethodGet, "/rate_limit"}, // Organizations - {"GET", "/users/:user/orgs"}, - {"GET", "/user/orgs"}, - {"GET", "/orgs/:org"}, - //{"PATCH", "/orgs/:org"}, - {"GET", "/orgs/:org/members"}, - {"GET", "/orgs/:org/members/:user"}, - {"DELETE", "/orgs/:org/members/:user"}, - {"GET", "/orgs/:org/public_members"}, - {"GET", "/orgs/:org/public_members/:user"}, - {"PUT", "/orgs/:org/public_members/:user"}, - {"DELETE", "/orgs/:org/public_members/:user"}, - {"GET", "/orgs/:org/teams"}, - {"GET", "/teams/:id"}, - {"POST", "/orgs/:org/teams"}, - //{"PATCH", "/teams/:id"}, - {"DELETE", "/teams/:id"}, - {"GET", "/teams/:id/members"}, - {"GET", "/teams/:id/members/:user"}, - {"PUT", "/teams/:id/members/:user"}, - {"DELETE", "/teams/:id/members/:user"}, - {"GET", "/teams/:id/repos"}, - {"GET", "/teams/:id/repos/:owner/:repo"}, - {"PUT", "/teams/:id/repos/:owner/:repo"}, - {"DELETE", "/teams/:id/repos/:owner/:repo"}, - {"GET", "/user/teams"}, + {http.MethodGet, "/users/:user/orgs"}, + {http.MethodGet, "/user/orgs"}, + {http.MethodGet, "/orgs/:org"}, + //{http.MethodPatch, "/orgs/:org"}, + {http.MethodGet, "/orgs/:org/members"}, + {http.MethodGet, "/orgs/:org/members/:user"}, + {http.MethodDelete, "/orgs/:org/members/:user"}, + {http.MethodGet, "/orgs/:org/public_members"}, + {http.MethodGet, "/orgs/:org/public_members/:user"}, + {http.MethodPut, "/orgs/:org/public_members/:user"}, + {http.MethodDelete, "/orgs/:org/public_members/:user"}, + {http.MethodGet, "/orgs/:org/teams"}, + {http.MethodGet, "/teams/:id"}, + {http.MethodPost, "/orgs/:org/teams"}, + //{http.MethodPatch, "/teams/:id"}, + {http.MethodDelete, "/teams/:id"}, + {http.MethodGet, "/teams/:id/members"}, + {http.MethodGet, "/teams/:id/members/:user"}, + {http.MethodPut, "/teams/:id/members/:user"}, + {http.MethodDelete, "/teams/:id/members/:user"}, + {http.MethodGet, "/teams/:id/repos"}, + {http.MethodGet, "/teams/:id/repos/:owner/:repo"}, + {http.MethodPut, "/teams/:id/repos/:owner/:repo"}, + {http.MethodDelete, "/teams/:id/repos/:owner/:repo"}, + {http.MethodGet, "/user/teams"}, // Pull Requests - {"GET", "/repos/:owner/:repo/pulls"}, - {"GET", "/repos/:owner/:repo/pulls/:number"}, - {"POST", "/repos/:owner/:repo/pulls"}, - //{"PATCH", "/repos/:owner/:repo/pulls/:number"}, - {"GET", "/repos/:owner/:repo/pulls/:number/commits"}, - {"GET", "/repos/:owner/:repo/pulls/:number/files"}, - {"GET", "/repos/:owner/:repo/pulls/:number/merge"}, - {"PUT", "/repos/:owner/:repo/pulls/:number/merge"}, - {"GET", "/repos/:owner/:repo/pulls/:number/comments"}, - //{"GET", "/repos/:owner/:repo/pulls/comments"}, - //{"GET", "/repos/:owner/:repo/pulls/comments/:number"}, - {"PUT", "/repos/:owner/:repo/pulls/:number/comments"}, - //{"PATCH", "/repos/:owner/:repo/pulls/comments/:number"}, - //{"DELETE", "/repos/:owner/:repo/pulls/comments/:number"}, + {http.MethodGet, "/repos/:owner/:repo/pulls"}, + {http.MethodGet, "/repos/:owner/:repo/pulls/:number"}, + {http.MethodPost, "/repos/:owner/:repo/pulls"}, + //{http.MethodPatch, "/repos/:owner/:repo/pulls/:number"}, + {http.MethodGet, "/repos/:owner/:repo/pulls/:number/commits"}, + {http.MethodGet, "/repos/:owner/:repo/pulls/:number/files"}, + {http.MethodGet, "/repos/:owner/:repo/pulls/:number/merge"}, + {http.MethodPut, "/repos/:owner/:repo/pulls/:number/merge"}, + {http.MethodGet, "/repos/:owner/:repo/pulls/:number/comments"}, + //{http.MethodGet, "/repos/:owner/:repo/pulls/comments"}, + //{http.MethodGet, "/repos/:owner/:repo/pulls/comments/:number"}, + {http.MethodPut, "/repos/:owner/:repo/pulls/:number/comments"}, + //{http.MethodPatch, "/repos/:owner/:repo/pulls/comments/:number"}, + //{http.MethodDelete, "/repos/:owner/:repo/pulls/comments/:number"}, // Repositories - {"GET", "/user/repos"}, - {"GET", "/users/:user/repos"}, - {"GET", "/orgs/:org/repos"}, - {"GET", "/repositories"}, - {"POST", "/user/repos"}, - {"POST", "/orgs/:org/repos"}, - {"GET", "/repos/:owner/:repo"}, - //{"PATCH", "/repos/:owner/:repo"}, - {"GET", "/repos/:owner/:repo/contributors"}, - {"GET", "/repos/:owner/:repo/languages"}, - {"GET", "/repos/:owner/:repo/teams"}, - {"GET", "/repos/:owner/:repo/tags"}, - {"GET", "/repos/:owner/:repo/branches"}, - {"GET", "/repos/:owner/:repo/branches/:branch"}, - {"DELETE", "/repos/:owner/:repo"}, - {"GET", "/repos/:owner/:repo/collaborators"}, - {"GET", "/repos/:owner/:repo/collaborators/:user"}, - {"PUT", "/repos/:owner/:repo/collaborators/:user"}, - {"DELETE", "/repos/:owner/:repo/collaborators/:user"}, - {"GET", "/repos/:owner/:repo/comments"}, - {"GET", "/repos/:owner/:repo/commits/:sha/comments"}, - {"POST", "/repos/:owner/:repo/commits/:sha/comments"}, - {"GET", "/repos/:owner/:repo/comments/:id"}, - //{"PATCH", "/repos/:owner/:repo/comments/:id"}, - {"DELETE", "/repos/:owner/:repo/comments/:id"}, - {"GET", "/repos/:owner/:repo/commits"}, - {"GET", "/repos/:owner/:repo/commits/:sha"}, - {"GET", "/repos/:owner/:repo/readme"}, - //{"GET", "/repos/:owner/:repo/contents/*path"}, - //{"PUT", "/repos/:owner/:repo/contents/*path"}, - //{"DELETE", "/repos/:owner/:repo/contents/*path"}, - //{"GET", "/repos/:owner/:repo/:archive_format/:ref"}, - {"GET", "/repos/:owner/:repo/keys"}, - {"GET", "/repos/:owner/:repo/keys/:id"}, - {"POST", "/repos/:owner/:repo/keys"}, - //{"PATCH", "/repos/:owner/:repo/keys/:id"}, - {"DELETE", "/repos/:owner/:repo/keys/:id"}, - {"GET", "/repos/:owner/:repo/downloads"}, - {"GET", "/repos/:owner/:repo/downloads/:id"}, - {"DELETE", "/repos/:owner/:repo/downloads/:id"}, - {"GET", "/repos/:owner/:repo/forks"}, - {"POST", "/repos/:owner/:repo/forks"}, - {"GET", "/repos/:owner/:repo/hooks"}, - {"GET", "/repos/:owner/:repo/hooks/:id"}, - {"POST", "/repos/:owner/:repo/hooks"}, - //{"PATCH", "/repos/:owner/:repo/hooks/:id"}, - {"POST", "/repos/:owner/:repo/hooks/:id/tests"}, - {"DELETE", "/repos/:owner/:repo/hooks/:id"}, - {"POST", "/repos/:owner/:repo/merges"}, - {"GET", "/repos/:owner/:repo/releases"}, - {"GET", "/repos/:owner/:repo/releases/:id"}, - {"POST", "/repos/:owner/:repo/releases"}, - //{"PATCH", "/repos/:owner/:repo/releases/:id"}, - {"DELETE", "/repos/:owner/:repo/releases/:id"}, - {"GET", "/repos/:owner/:repo/releases/:id/assets"}, - {"GET", "/repos/:owner/:repo/stats/contributors"}, - {"GET", "/repos/:owner/:repo/stats/commit_activity"}, - {"GET", "/repos/:owner/:repo/stats/code_frequency"}, - {"GET", "/repos/:owner/:repo/stats/participation"}, - {"GET", "/repos/:owner/:repo/stats/punch_card"}, - {"GET", "/repos/:owner/:repo/statuses/:ref"}, - {"POST", "/repos/:owner/:repo/statuses/:ref"}, + {http.MethodGet, "/user/repos"}, + {http.MethodGet, "/users/:user/repos"}, + {http.MethodGet, "/orgs/:org/repos"}, + {http.MethodGet, "/repositories"}, + {http.MethodPost, "/user/repos"}, + {http.MethodPost, "/orgs/:org/repos"}, + {http.MethodGet, "/repos/:owner/:repo"}, + //{http.MethodPatch, "/repos/:owner/:repo"}, + {http.MethodGet, "/repos/:owner/:repo/contributors"}, + {http.MethodGet, "/repos/:owner/:repo/languages"}, + {http.MethodGet, "/repos/:owner/:repo/teams"}, + {http.MethodGet, "/repos/:owner/:repo/tags"}, + {http.MethodGet, "/repos/:owner/:repo/branches"}, + {http.MethodGet, "/repos/:owner/:repo/branches/:branch"}, + {http.MethodDelete, "/repos/:owner/:repo"}, + {http.MethodGet, "/repos/:owner/:repo/collaborators"}, + {http.MethodGet, "/repos/:owner/:repo/collaborators/:user"}, + {http.MethodPut, "/repos/:owner/:repo/collaborators/:user"}, + {http.MethodDelete, "/repos/:owner/:repo/collaborators/:user"}, + {http.MethodGet, "/repos/:owner/:repo/comments"}, + {http.MethodGet, "/repos/:owner/:repo/commits/:sha/comments"}, + {http.MethodPost, "/repos/:owner/:repo/commits/:sha/comments"}, + {http.MethodGet, "/repos/:owner/:repo/comments/:id"}, + //{http.MethodPatch, "/repos/:owner/:repo/comments/:id"}, + {http.MethodDelete, "/repos/:owner/:repo/comments/:id"}, + {http.MethodGet, "/repos/:owner/:repo/commits"}, + {http.MethodGet, "/repos/:owner/:repo/commits/:sha"}, + {http.MethodGet, "/repos/:owner/:repo/readme"}, + //{http.MethodGet, "/repos/:owner/:repo/contents/*path"}, + //{http.MethodPut, "/repos/:owner/:repo/contents/*path"}, + //{http.MethodDelete, "/repos/:owner/:repo/contents/*path"}, + //{http.MethodGet, "/repos/:owner/:repo/:archive_format/:ref"}, + {http.MethodGet, "/repos/:owner/:repo/keys"}, + {http.MethodGet, "/repos/:owner/:repo/keys/:id"}, + {http.MethodPost, "/repos/:owner/:repo/keys"}, + //{http.MethodPatch, "/repos/:owner/:repo/keys/:id"}, + {http.MethodDelete, "/repos/:owner/:repo/keys/:id"}, + {http.MethodGet, "/repos/:owner/:repo/downloads"}, + {http.MethodGet, "/repos/:owner/:repo/downloads/:id"}, + {http.MethodDelete, "/repos/:owner/:repo/downloads/:id"}, + {http.MethodGet, "/repos/:owner/:repo/forks"}, + {http.MethodPost, "/repos/:owner/:repo/forks"}, + {http.MethodGet, "/repos/:owner/:repo/hooks"}, + {http.MethodGet, "/repos/:owner/:repo/hooks/:id"}, + {http.MethodPost, "/repos/:owner/:repo/hooks"}, + //{http.MethodPatch, "/repos/:owner/:repo/hooks/:id"}, + {http.MethodPost, "/repos/:owner/:repo/hooks/:id/tests"}, + {http.MethodDelete, "/repos/:owner/:repo/hooks/:id"}, + {http.MethodPost, "/repos/:owner/:repo/merges"}, + {http.MethodGet, "/repos/:owner/:repo/releases"}, + {http.MethodGet, "/repos/:owner/:repo/releases/:id"}, + {http.MethodPost, "/repos/:owner/:repo/releases"}, + //{http.MethodPatch, "/repos/:owner/:repo/releases/:id"}, + {http.MethodDelete, "/repos/:owner/:repo/releases/:id"}, + {http.MethodGet, "/repos/:owner/:repo/releases/:id/assets"}, + {http.MethodGet, "/repos/:owner/:repo/stats/contributors"}, + {http.MethodGet, "/repos/:owner/:repo/stats/commit_activity"}, + {http.MethodGet, "/repos/:owner/:repo/stats/code_frequency"}, + {http.MethodGet, "/repos/:owner/:repo/stats/participation"}, + {http.MethodGet, "/repos/:owner/:repo/stats/punch_card"}, + {http.MethodGet, "/repos/:owner/:repo/statuses/:ref"}, + {http.MethodPost, "/repos/:owner/:repo/statuses/:ref"}, // Search - {"GET", "/search/repositories"}, - {"GET", "/search/code"}, - {"GET", "/search/issues"}, - {"GET", "/search/users"}, - {"GET", "/legacy/issues/search/:owner/:repository/:state/:keyword"}, - {"GET", "/legacy/repos/search/:keyword"}, - {"GET", "/legacy/user/search/:keyword"}, - {"GET", "/legacy/user/email/:email"}, + {http.MethodGet, "/search/repositories"}, + {http.MethodGet, "/search/code"}, + {http.MethodGet, "/search/issues"}, + {http.MethodGet, "/search/users"}, + {http.MethodGet, "/legacy/issues/search/:owner/:repository/:state/:keyword"}, + {http.MethodGet, "/legacy/repos/search/:keyword"}, + {http.MethodGet, "/legacy/user/search/:keyword"}, + {http.MethodGet, "/legacy/user/email/:email"}, // Users - {"GET", "/users/:user"}, - {"GET", "/user"}, - //{"PATCH", "/user"}, - {"GET", "/users"}, - {"GET", "/user/emails"}, - {"POST", "/user/emails"}, - {"DELETE", "/user/emails"}, - {"GET", "/users/:user/followers"}, - {"GET", "/user/followers"}, - {"GET", "/users/:user/following"}, - {"GET", "/user/following"}, - {"GET", "/user/following/:user"}, - {"GET", "/users/:user/following/:target_user"}, - {"PUT", "/user/following/:user"}, - {"DELETE", "/user/following/:user"}, - {"GET", "/users/:user/keys"}, - {"GET", "/user/keys"}, - {"GET", "/user/keys/:id"}, - {"POST", "/user/keys"}, - //{"PATCH", "/user/keys/:id"}, - {"DELETE", "/user/keys/:id"}, + {http.MethodGet, "/users/:user"}, + {http.MethodGet, "/user"}, + //{http.MethodPatch, "/user"}, + {http.MethodGet, "/users"}, + {http.MethodGet, "/user/emails"}, + {http.MethodPost, "/user/emails"}, + {http.MethodDelete, "/user/emails"}, + {http.MethodGet, "/users/:user/followers"}, + {http.MethodGet, "/user/followers"}, + {http.MethodGet, "/users/:user/following"}, + {http.MethodGet, "/user/following"}, + {http.MethodGet, "/user/following/:user"}, + {http.MethodGet, "/users/:user/following/:target_user"}, + {http.MethodPut, "/user/following/:user"}, + {http.MethodDelete, "/user/following/:user"}, + {http.MethodGet, "/users/:user/keys"}, + {http.MethodGet, "/user/keys"}, + {http.MethodGet, "/user/keys/:id"}, + {http.MethodPost, "/user/keys"}, + //{http.MethodPatch, "/user/keys/:id"}, + {http.MethodDelete, "/user/keys/:id"}, } func TestShouldBindUri(t *testing.T) { @@ -293,7 +293,7 @@ func TestShouldBindUri(t *testing.T) { Name string `uri:"name" binding:"required"` Id string `uri:"id" binding:"required"` } - router.Handle("GET", "/rest/:name/:id", func(c *Context) { + router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person assert.NoError(t, c.ShouldBindUri(&person)) assert.True(t, "" != person.Name) @@ -302,7 +302,7 @@ func TestShouldBindUri(t *testing.T) { }) path, _ := exampleFromPath("/rest/:name/:id") - w := performRequest(router, "GET", path) + w := performRequest(router, http.MethodGet, path) assert.Equal(t, "ShouldBindUri test OK", w.Body.String()) assert.Equal(t, http.StatusOK, w.Code) } @@ -315,7 +315,7 @@ func TestBindUri(t *testing.T) { Name string `uri:"name" binding:"required"` Id string `uri:"id" binding:"required"` } - router.Handle("GET", "/rest/:name/:id", func(c *Context) { + router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person assert.NoError(t, c.BindUri(&person)) assert.True(t, "" != person.Name) @@ -324,7 +324,7 @@ func TestBindUri(t *testing.T) { }) path, _ := exampleFromPath("/rest/:name/:id") - w := performRequest(router, "GET", path) + w := performRequest(router, http.MethodGet, path) assert.Equal(t, "BindUri test OK", w.Body.String()) assert.Equal(t, http.StatusOK, w.Code) } @@ -336,13 +336,13 @@ func TestBindUriError(t *testing.T) { type Member struct { Number string `uri:"num" binding:"required,uuid"` } - router.Handle("GET", "/new/rest/:num", func(c *Context) { + router.Handle(http.MethodGet, "/new/rest/:num", func(c *Context) { var m Member assert.Error(t, c.BindUri(&m)) }) path1, _ := exampleFromPath("/new/rest/:num") - w1 := performRequest(router, "GET", path1) + w1 := performRequest(router, http.MethodGet, path1) assert.Equal(t, http.StatusBadRequest, w1.Code) } @@ -358,7 +358,7 @@ func TestRaceContextCopy(t *testing.T) { go readWriteKeys(c.Copy()) c.String(http.StatusOK, "run OK, no panics") }) - w := performRequest(router, "GET", "/test/copy/race") + w := performRequest(router, http.MethodGet, "/test/copy/race") assert.Equal(t, "run OK, no panics", w.Body.String()) } @@ -438,7 +438,7 @@ func exampleFromPath(path string) (string, Params) { func BenchmarkGithub(b *testing.B) { router := New() githubConfigRouter(router) - runRequest(b, router, "GET", "/legacy/issues/search/:owner/:repository/:state/:keyword") + runRequest(b, router, http.MethodGet, "/legacy/issues/search/:owner/:repository/:state/:keyword") } func BenchmarkParallelGithub(b *testing.B) { @@ -446,7 +446,7 @@ func BenchmarkParallelGithub(b *testing.B) { router := New() githubConfigRouter(router) - req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil) + req, _ := http.NewRequest(http.MethodPost, "/repos/manucorporat/sse/git/blobs", nil) b.RunParallel(func(pb *testing.PB) { // Each goroutine has its own bytes.Buffer. @@ -462,7 +462,7 @@ func BenchmarkParallelGithubDefault(b *testing.B) { router := New() githubConfigRouter(router) - req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil) + req, _ := http.NewRequest(http.MethodPost, "/repos/manucorporat/sse/git/blobs", nil) b.RunParallel(func(pb *testing.PB) { // Each goroutine has its own bytes.Buffer. diff --git a/logger.go b/logger.go index fcf90c2..d5b96b3 100644 --- a/logger.go +++ b/logger.go @@ -99,19 +99,19 @@ func (p *LogFormatterParams) MethodColor() string { method := p.Method switch method { - case "GET": + case http.MethodGet: return blue - case "POST": + case http.MethodPost: return cyan - case "PUT": + case http.MethodPut: return yellow - case "DELETE": + case http.MethodDelete: return red - case "PATCH": + case http.MethodPatch: return green - case "HEAD": + case http.MethodHead: return magenta - case "OPTIONS": + case http.MethodOptions: return white default: return reset diff --git a/routergroup.go b/routergroup.go index 2e7a5b9..9ff7c03 100644 --- a/routergroup.go +++ b/routergroup.go @@ -95,51 +95,51 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha // POST is a shortcut for router.Handle("POST", path, handle). func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { - return group.handle("POST", relativePath, handlers) + return group.handle(http.MethodPost, relativePath, handlers) } // GET is a shortcut for router.Handle("GET", path, handle). func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { - return group.handle("GET", relativePath, handlers) + return group.handle(http.MethodGet, relativePath, handlers) } // DELETE is a shortcut for router.Handle("DELETE", path, handle). func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { - return group.handle("DELETE", relativePath, handlers) + return group.handle(http.MethodDelete, relativePath, handlers) } // PATCH is a shortcut for router.Handle("PATCH", path, handle). func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { - return group.handle("PATCH", relativePath, handlers) + return group.handle(http.MethodPatch, relativePath, handlers) } // PUT is a shortcut for router.Handle("PUT", path, handle). func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes { - return group.handle("PUT", relativePath, handlers) + return group.handle(http.MethodPut, relativePath, handlers) } // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle). func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { - return group.handle("OPTIONS", relativePath, handlers) + return group.handle(http.MethodOptions, relativePath, handlers) } // HEAD is a shortcut for router.Handle("HEAD", path, handle). func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { - return group.handle("HEAD", relativePath, handlers) + return group.handle(http.MethodHead, relativePath, handlers) } // Any registers a route that matches all the HTTP methods. // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { - group.handle("GET", relativePath, handlers) - group.handle("POST", relativePath, handlers) - group.handle("PUT", relativePath, handlers) - group.handle("PATCH", relativePath, handlers) - group.handle("HEAD", relativePath, handlers) - group.handle("OPTIONS", relativePath, handlers) - group.handle("DELETE", relativePath, handlers) - group.handle("CONNECT", relativePath, handlers) - group.handle("TRACE", relativePath, handlers) + group.handle(http.MethodGet, relativePath, handlers) + group.handle(http.MethodPost, relativePath, handlers) + group.handle(http.MethodPut, relativePath, handlers) + group.handle(http.MethodPatch, relativePath, handlers) + group.handle(http.MethodHead, relativePath, handlers) + group.handle(http.MethodOptions, relativePath, handlers) + group.handle(http.MethodDelete, relativePath, handlers) + group.handle(http.MethodConnect, relativePath, handlers) + group.handle(http.MethodTrace, relativePath, handlers) return group.returnObj() } diff --git a/routergroup_test.go b/routergroup_test.go index ce3d54a..0e49d65 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -33,13 +33,13 @@ func TestRouterGroupBasic(t *testing.T) { } func TestRouterGroupBasicHandle(t *testing.T) { - performRequestInGroup(t, "GET") - performRequestInGroup(t, "POST") - performRequestInGroup(t, "PUT") - performRequestInGroup(t, "PATCH") - performRequestInGroup(t, "DELETE") - performRequestInGroup(t, "HEAD") - performRequestInGroup(t, "OPTIONS") + performRequestInGroup(t, http.MethodGet) + performRequestInGroup(t, http.MethodPost) + performRequestInGroup(t, http.MethodPut) + performRequestInGroup(t, http.MethodPatch) + performRequestInGroup(t, http.MethodDelete) + performRequestInGroup(t, http.MethodHead) + performRequestInGroup(t, http.MethodOptions) } func performRequestInGroup(t *testing.T, method string) { @@ -55,25 +55,25 @@ func performRequestInGroup(t *testing.T, method string) { } switch method { - case "GET": + case http.MethodGet: v1.GET("/test", handler) login.GET("/test", handler) - case "POST": + case http.MethodPost: v1.POST("/test", handler) login.POST("/test", handler) - case "PUT": + case http.MethodPut: v1.PUT("/test", handler) login.PUT("/test", handler) - case "PATCH": + case http.MethodPatch: v1.PATCH("/test", handler) login.PATCH("/test", handler) - case "DELETE": + case http.MethodDelete: v1.DELETE("/test", handler) login.DELETE("/test", handler) - case "HEAD": + case http.MethodHead: v1.HEAD("/test", handler) login.HEAD("/test", handler) - case "OPTIONS": + case http.MethodOptions: v1.OPTIONS("/test", handler) login.OPTIONS("/test", handler) default: @@ -128,7 +128,7 @@ func TestRouterGroupTooManyHandlers(t *testing.T) { func TestRouterGroupBadMethod(t *testing.T) { router := New() assert.Panics(t, func() { - router.Handle("get", "/") + router.Handle(http.MethodGet, "/") }) assert.Panics(t, func() { router.Handle(" GET", "/") @@ -162,7 +162,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) { handler := func(c *Context) {} assert.Equal(t, r, r.Use(handler)) - assert.Equal(t, r, r.Handle("GET", "/handler", handler)) + assert.Equal(t, r, r.Handle(http.MethodGet, "/handler", handler)) assert.Equal(t, r, r.Any("/any", handler)) assert.Equal(t, r, r.GET("/", handler)) assert.Equal(t, r, r.POST("/", handler)) diff --git a/routes_test.go b/routes_test.go index d1ddbe9..ee6ea82 100644 --- a/routes_test.go +++ b/routes_test.go @@ -70,10 +70,10 @@ func testRouteNotOK2(method string, t *testing.T) { router := New() router.HandleMethodNotAllowed = true var methodRoute string - if method == "POST" { - methodRoute = "GET" + if method == http.MethodPost { + methodRoute = http.MethodGet } else { - methodRoute = "POST" + methodRoute = http.MethodPost } router.Handle(methodRoute, "/test", func(c *Context) { passed = true @@ -99,46 +99,46 @@ func TestRouterMethod(t *testing.T) { c.String(http.StatusOK, "sup3") }) - w := performRequest(router, "PUT", "/hey") + w := performRequest(router, http.MethodPut, "/hey") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "called", w.Body.String()) } func TestRouterGroupRouteOK(t *testing.T) { - testRouteOK("GET", t) - testRouteOK("POST", t) - testRouteOK("PUT", t) - testRouteOK("PATCH", t) - testRouteOK("HEAD", t) - testRouteOK("OPTIONS", t) - testRouteOK("DELETE", t) - testRouteOK("CONNECT", t) - testRouteOK("TRACE", t) + testRouteOK(http.MethodGet, t) + testRouteOK(http.MethodPost, t) + testRouteOK(http.MethodPut, t) + testRouteOK(http.MethodPatch, t) + testRouteOK(http.MethodHead, t) + testRouteOK(http.MethodOptions, t) + testRouteOK(http.MethodDelete, t) + testRouteOK(http.MethodConnect, t) + testRouteOK(http.MethodTrace, t) } func TestRouteNotOK(t *testing.T) { - testRouteNotOK("GET", t) - testRouteNotOK("POST", t) - testRouteNotOK("PUT", t) - testRouteNotOK("PATCH", t) - testRouteNotOK("HEAD", t) - testRouteNotOK("OPTIONS", t) - testRouteNotOK("DELETE", t) - testRouteNotOK("CONNECT", t) - testRouteNotOK("TRACE", t) + testRouteNotOK(http.MethodGet, t) + testRouteNotOK(http.MethodPost, t) + testRouteNotOK(http.MethodPut, t) + testRouteNotOK(http.MethodPatch, t) + testRouteNotOK(http.MethodHead, t) + testRouteNotOK(http.MethodOptions, t) + testRouteNotOK(http.MethodDelete, t) + testRouteNotOK(http.MethodConnect, t) + testRouteNotOK(http.MethodTrace, t) } func TestRouteNotOK2(t *testing.T) { - testRouteNotOK2("GET", t) - testRouteNotOK2("POST", t) - testRouteNotOK2("PUT", t) - testRouteNotOK2("PATCH", t) - testRouteNotOK2("HEAD", t) - testRouteNotOK2("OPTIONS", t) - testRouteNotOK2("DELETE", t) - testRouteNotOK2("CONNECT", t) - testRouteNotOK2("TRACE", t) + testRouteNotOK2(http.MethodGet, t) + testRouteNotOK2(http.MethodPost, t) + testRouteNotOK2(http.MethodPut, t) + testRouteNotOK2(http.MethodPatch, t) + testRouteNotOK2(http.MethodHead, t) + testRouteNotOK2(http.MethodOptions, t) + testRouteNotOK2(http.MethodDelete, t) + testRouteNotOK2(http.MethodConnect, t) + testRouteNotOK2(http.MethodTrace, t) } func TestRouteRedirectTrailingSlash(t *testing.T) { @@ -150,50 +150,50 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { router.POST("/path3", func(c *Context) {}) router.PUT("/path4/", func(c *Context) {}) - w := performRequest(router, "GET", "/path/") + w := performRequest(router, http.MethodGet, "/path/") assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) - w = performRequest(router, "GET", "/path2") + w = performRequest(router, http.MethodGet, "/path2") assert.Equal(t, "/path2/", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) - w = performRequest(router, "POST", "/path3/") + w = performRequest(router, http.MethodPost, "/path3/") assert.Equal(t, "/path3", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) - w = performRequest(router, "PUT", "/path4") + w = performRequest(router, http.MethodPut, "/path4") assert.Equal(t, "/path4/", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) - w = performRequest(router, "GET", "/path") + w = performRequest(router, http.MethodGet, "/path") assert.Equal(t, http.StatusOK, w.Code) - w = performRequest(router, "GET", "/path2/") + w = performRequest(router, http.MethodGet, "/path2/") assert.Equal(t, http.StatusOK, w.Code) - w = performRequest(router, "POST", "/path3") + w = performRequest(router, http.MethodPost, "/path3") assert.Equal(t, http.StatusOK, w.Code) - w = performRequest(router, "PUT", "/path4/") + w = performRequest(router, http.MethodPut, "/path4/") assert.Equal(t, http.StatusOK, w.Code) - w = performRequest(router, "GET", "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) + w = performRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) assert.Equal(t, "/api/path2/", w.Header().Get("Location")) assert.Equal(t, 301, w.Code) - w = performRequest(router, "GET", "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) + w = performRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) assert.Equal(t, 200, w.Code) router.RedirectTrailingSlash = false - w = performRequest(router, "GET", "/path/") + w = performRequest(router, http.MethodGet, "/path/") assert.Equal(t, http.StatusNotFound, w.Code) - w = performRequest(router, "GET", "/path2") + w = performRequest(router, http.MethodGet, "/path2") assert.Equal(t, http.StatusNotFound, w.Code) - w = performRequest(router, "POST", "/path3/") + w = performRequest(router, http.MethodPost, "/path3/") assert.Equal(t, http.StatusNotFound, w.Code) - w = performRequest(router, "PUT", "/path4") + w = performRequest(router, http.MethodPut, "/path4") assert.Equal(t, http.StatusNotFound, w.Code) } @@ -207,19 +207,19 @@ func TestRouteRedirectFixedPath(t *testing.T) { router.POST("/PATH3", func(c *Context) {}) router.POST("/Path4/", func(c *Context) {}) - w := performRequest(router, "GET", "/PATH") + w := performRequest(router, http.MethodGet, "/PATH") assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) - w = performRequest(router, "GET", "/path2") + w = performRequest(router, http.MethodGet, "/path2") assert.Equal(t, "/Path2", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) - w = performRequest(router, "POST", "/path3") + w = performRequest(router, http.MethodPost, "/path3") assert.Equal(t, "/PATH3", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) - w = performRequest(router, "POST", "/path4") + w = performRequest(router, http.MethodPost, "/path4") assert.Equal(t, "/Path4/", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) } @@ -249,7 +249,7 @@ func TestRouteParamsByName(t *testing.T) { assert.False(t, ok) }) - w := performRequest(router, "GET", "/test/john/smith/is/super/great") + w := performRequest(router, http.MethodGet, "/test/john/smith/is/super/great") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "john", name) @@ -283,7 +283,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { assert.False(t, ok) }) - w := performRequest(router, "GET", "//test//john//smith//is//super//great") + w := performRequest(router, http.MethodGet, "//test//john//smith//is//super//great") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "john", name) @@ -311,16 +311,16 @@ func TestRouteStaticFile(t *testing.T) { router.Static("/using_static", dir) router.StaticFile("/result", f.Name()) - w := performRequest(router, "GET", "/using_static/"+filename) - w2 := performRequest(router, "GET", "/result") + w := performRequest(router, http.MethodGet, "/using_static/"+filename) + w2 := performRequest(router, http.MethodGet, "/result") assert.Equal(t, w, w2) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Gin Web Framework", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) - w3 := performRequest(router, "HEAD", "/using_static/"+filename) - w4 := performRequest(router, "HEAD", "/result") + w3 := performRequest(router, http.MethodHead, "/using_static/"+filename) + w4 := performRequest(router, http.MethodHead, "/result") assert.Equal(t, w3, w4) assert.Equal(t, http.StatusOK, w3.Code) @@ -331,7 +331,7 @@ func TestRouteStaticListingDir(t *testing.T) { router := New() router.StaticFS("/", Dir("./", true)) - w := performRequest(router, "GET", "/") + w := performRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "gin.go") @@ -343,7 +343,7 @@ func TestRouteStaticNoListing(t *testing.T) { router := New() router.Static("/", "./") - w := performRequest(router, "GET", "/") + w := performRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusNotFound, w.Code) assert.NotContains(t, w.Body.String(), "gin.go") @@ -358,7 +358,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { }) static.Static("/", "./") - w := performRequest(router, "GET", "/gin.go") + w := performRequest(router, http.MethodGet, "/gin.go") assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") @@ -372,13 +372,13 @@ func TestRouteNotAllowedEnabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = true router.POST("/path", func(c *Context) {}) - w := performRequest(router, "GET", "/path") + w := performRequest(router, http.MethodGet, "/path") assert.Equal(t, http.StatusMethodNotAllowed, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) - w = performRequest(router, "GET", "/path") + w = performRequest(router, http.MethodGet, "/path") assert.Equal(t, "responseText", w.Body.String()) assert.Equal(t, http.StatusTeapot, w.Code) } @@ -387,9 +387,9 @@ func TestRouteNotAllowedEnabled2(t *testing.T) { router := New() router.HandleMethodNotAllowed = true // add one methodTree to trees - router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}}) router.GET("/path2", func(c *Context) {}) - w := performRequest(router, "POST", "/path2") + w := performRequest(router, http.MethodPost, "/path2") assert.Equal(t, http.StatusMethodNotAllowed, w.Code) } @@ -397,13 +397,13 @@ func TestRouteNotAllowedDisabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = false router.POST("/path", func(c *Context) {}) - w := performRequest(router, "GET", "/path") + w := performRequest(router, http.MethodGet, "/path") assert.Equal(t, http.StatusNotFound, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) - w = performRequest(router, "GET", "/path") + w = performRequest(router, http.MethodGet, "/path") assert.Equal(t, "404 page not found", w.Body.String()) assert.Equal(t, http.StatusNotFound, w.Code) } @@ -453,7 +453,7 @@ func TestRouterNotFound(t *testing.T) { {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { - w := performRequest(router, "GET", tr.route) + w := performRequest(router, http.MethodGet, tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) @@ -466,20 +466,20 @@ func TestRouterNotFound(t *testing.T) { c.AbortWithStatus(http.StatusNotFound) notFound = true }) - w := performRequest(router, "GET", "/nope") + w := performRequest(router, http.MethodGet, "/nope") assert.Equal(t, http.StatusNotFound, w.Code) assert.True(t, notFound) // Test other method than GET (want 307 instead of 301) router.PATCH("/path", func(c *Context) {}) - w = performRequest(router, "PATCH", "/path/") + w = performRequest(router, http.MethodPatch, "/path/") assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header())) // Test special case where no node for the prefix "/" exists router = New() router.GET("/a", func(c *Context) {}) - w = performRequest(router, "GET", "/") + w = performRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusNotFound, w.Code) } @@ -490,10 +490,10 @@ func TestRouterStaticFSNotFound(t *testing.T) { c.String(404, "non existent") }) - w := performRequest(router, "GET", "/nonexistent") + w := performRequest(router, http.MethodGet, "/nonexistent") assert.Equal(t, "non existent", w.Body.String()) - w = performRequest(router, "HEAD", "/nonexistent") + w = performRequest(router, http.MethodHead, "/nonexistent") assert.Equal(t, "non existent", w.Body.String()) } @@ -503,7 +503,7 @@ func TestRouterStaticFSFileNotFound(t *testing.T) { router.StaticFS("/", http.FileSystem(http.Dir("."))) assert.NotPanics(t, func() { - performRequest(router, "GET", "/nonexistent") + performRequest(router, http.MethodGet, "/nonexistent") }) } @@ -520,11 +520,11 @@ func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) { router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) // First access - performRequest(router, "GET", "/nonexistent") + performRequest(router, http.MethodGet, "/nonexistent") assert.Equal(t, 1, middlewareCalledNum) // Second access - performRequest(router, "HEAD", "/nonexistent") + performRequest(router, http.MethodHead, "/nonexistent") assert.Equal(t, 2, middlewareCalledNum) } @@ -543,7 +543,7 @@ func TestRouteRawPath(t *testing.T) { assert.Equal(t, "222", num) }) - w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/222") + w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222") assert.Equal(t, http.StatusOK, w.Code) } @@ -563,7 +563,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) { assert.Equal(t, "333", num) }) - w := performRequest(route, "POST", "/project/Some%2FOther%2FProject/build/333") + w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333") assert.Equal(t, http.StatusOK, w.Code) } @@ -574,7 +574,7 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) { c.Next() }) - w := performRequest(route, "GET", "/NotFound") + w := performRequest(route, http.MethodGet, "/NotFound") assert.Equal(t, 421, w.Code) assert.Equal(t, 0, w.Body.Len()) } @@ -605,7 +605,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) { } for _, route := range routes { - w := performRequest(router, "GET", route) + w := performRequest(router, http.MethodGet, route) assert.Equal(t, http.StatusOK, w.Code) } @@ -615,6 +615,6 @@ func TestRouteContextHoldsFullPath(t *testing.T) { assert.Equal(t, "", c.FullPath()) }) - w := performRequest(router, "GET", "/not-found") + w := performRequest(router, http.MethodGet, "/not-found") assert.Equal(t, http.StatusNotFound, w.Code) } From 3957f6bb4b84a86c6a6694b0b6af0b8496ae2207 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 29 Nov 2019 13:07:19 +0800 Subject: [PATCH 422/912] docs(benchmarks): for gin v1.5 (#2153) --- BENCHMARKS.md | 1151 ++++++++++++++++++++++++++----------------------- 1 file changed, 620 insertions(+), 531 deletions(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 9a7df86..e4ff467 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -2,603 +2,692 @@ ## 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) +**Machine:** 12 CPU, 24 GB RAM. Ubuntu 16.04.2 x64 +**Date:** Nov 26th, 2019 +**Go Version:** 1.13.4 linux/amd64 +**Source:** [Go HTTP Router Benchmark](https://github.com/julienschmidt/go-http-routing-benchmark) +**Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) ## Static Routes: 157 ``` -Gin: 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 +Gin: 34936 Bytes + +HttpServeMux: 14512 Bytes +Ace: 30648 Bytes +Aero: 800696 Bytes +Bear: 30664 Bytes +Beego: 98456 Bytes +Bone: 40224 Bytes +Chi: 83608 Bytes +CloudyKitRouter: 30448 Bytes +Denco: 9928 Bytes +Echo: 76584 Bytes +GocraftWeb: 55496 Bytes +Goji: 29744 Bytes +Gojiv2: 105840 Bytes +GoJsonRest: 137512 Bytes +GoRestful: 816936 Bytes +GorillaMux: 585632 Bytes +GowwwRouter: 24968 Bytes +HttpRouter: 21680 Bytes +HttpTreeMux: 73448 Bytes +Kocha: 115472 Bytes +LARS: 30640 Bytes +Macaron: 38592 Bytes +Martini: 310864 Bytes +Pat: 19696 Bytes +Possum: 89920 Bytes +R2router: 23712 Bytes +Rivet: 24608 Bytes +Tango: 28264 Bytes +TigerTonic: 78768 Bytes +Traffic: 538976 Bytes +Vulcan: 369960 Bytes ``` ## GithubAPI Routes: 203 ``` -Gin: 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 +Gin: 58512 Bytes + +Ace: 48640 Bytes +Aero: 1386208 Bytes +Bear: 82536 Bytes +Beego: 150936 Bytes +Bone: 100976 Bytes +Chi: 95112 Bytes +CloudyKitRouter: 93704 Bytes +Denco: 36736 Bytes +Echo: 96328 Bytes +GocraftWeb: 95432 Bytes +Goji: 51600 Bytes +Gojiv2: 104704 Bytes +GoJsonRest: 142024 Bytes +GoRestful: 1241656 Bytes +GorillaMux: 1322784 Bytes +GowwwRouter: 80008 Bytes +HttpRouter: 37096 Bytes +HttpTreeMux: 78800 Bytes +Kocha: 785408 Bytes +LARS: 48600 Bytes +Macaron: 93680 Bytes +Martini: 485264 Bytes +Pat: 21200 Bytes +Possum: 85312 Bytes +R2router: 47104 Bytes +Rivet: 42840 Bytes +Tango: 54840 Bytes +TigerTonic: 96176 Bytes +Traffic: 921744 Bytes +Vulcan: 425368 Bytes ``` ## GPlusAPI Routes: 13 ``` -Gin: 3968 Bytes +Gin: 4384 Bytes -Ace: 3600 Bytes +Ace: 3 664 Bytes +Aero: 88248 Bytes Bear: 7112 Bytes -Beego: 10048 Bytes -Bone: 6480 Bytes -Denco: 3256 Bytes -Echo: 9000 Bytes +Beego: 10272 Bytes +Bone: 6688 Bytes +Chi: 8024 Bytes +CloudyKitRouter: 6728 Bytes +Denco: 3264 Bytes +Echo: 9272 Bytes GocraftWeb: 7496 Bytes -Goji: 2912 Bytes +Goji: 3152 Bytes Gojiv2: 7376 Bytes -GoJsonRest: 11544 Bytes -GoRestful: 88776 Bytes -GorillaMux: 71488 Bytes -HttpRouter: 2712 Bytes +GoJsonRest: 11416 Bytes +GoRestful: 74328 Bytes +GorillaMux: 66208 Bytes +GowwwRouter: 5744 Bytes +HttpRouter: 2760 Bytes HttpTreeMux: 7440 Bytes Kocha: 128880 Bytes -LARS: 3640 Bytes +LARS: 3656 Bytes Macaron: 8656 Bytes -Martini: 23936 Bytes +Martini: 23920 Bytes Pat: 1856 Bytes Possum: 7248 Bytes R2router: 3928 Bytes Rivet: 3064 Bytes -Tango: 4912 Bytes +Tango: 5168 Bytes TigerTonic: 9408 Bytes -Traffic: 49472 Bytes -Vulcan: 25496 Bytes +Traffic: 46400 Bytes +Vulcan: 25544 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 +Gin: 7776 Bytes + +Ace: 6656 Bytes +Aero: 163736 Bytes +Bear: 12528 Bytes +Beego: 19280 Bytes +Bone: 11440 Bytes +Chi: 9744 Bytes +Denco: 4192 Bytes +Echo: 11648 Bytes GocraftWeb: 12800 Bytes -Goji: 5232 Bytes +Goji: 5680 Bytes Gojiv2: 14464 Bytes -GoJsonRest: 14216 Bytes -GoRestful: 127368 Bytes -GorillaMux: 123016 Bytes -HttpRouter: 4976 Bytes +GoJsonRest: 14424 Bytes +GoRestful: 116264 Bytes +GorillaMux: 105880 Bytes +GowwwRouter: 9344 Bytes +HttpRouter: 5024 Bytes HttpTreeMux: 7848 Bytes Kocha: 181712 Bytes LARS: 6632 Bytes Macaron: 13648 Bytes -Martini: 45952 Bytes +Martini: 45888 Bytes Pat: 2560 Bytes Possum: 9200 Bytes R2router: 7056 Bytes Rivet: 5680 Bytes -Tango: 8664 Bytes +Tango: 8920 Bytes TigerTonic: 9840 Bytes -Traffic: 93480 Bytes +Traffic: 79096 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 +BenchmarkGin_StaticAll 25604 45487 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_StaticAll 28402 42046 ns/op 0 B/op 0 allocs/op +BenchmarkAero_StaticAll 38766 30333 ns/op 0 B/op 0 allocs/op +BenchmarkHttpServeMux_StaticAll 25728 46511 ns/op 0 B/op 0 allocs/op +BenchmarkBeego_StaticAll 5098 288527 ns/op 55264 B/op 471 allocs/op +BenchmarkBear_StaticAll 10000 126323 ns/op 20272 B/op 469 allocs/op +BenchmarkBone_StaticAll 9499 113631 ns/op 0 B/op 0 allocs/op +BenchmarkChi_StaticAll 7912 237363 ns/op 67824 B/op 471 allocs/op +BenchmarkCloudyKitRouter_StaticAll 41626 28668 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_StaticAll 95774 12221 ns/op 0 B/op 0 allocs/op +BenchmarkEcho_StaticAll 26246 44603 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_StaticAll 10000 193337 ns/op 46312 B/op 785 allocs/op +BenchmarkGoji_StaticAll 15886 75789 ns/op 0 B/op 0 allocs/op +BenchmarkGojiv2_StaticAll 1886 597374 ns/op 205984 B/op 1570 allocs/op +BenchmarkGoJsonRest_StaticAll 4700 307144 ns/op 51653 B/op 1727 allocs/op +BenchmarkGoRestful_StaticAll 429 2880165 ns/op 613280 B/op 2053 allocs/op +BenchmarkGorillaMux_StaticAll 754 1491761 ns/op 153233 B/op 1413 allocs/op +BenchmarkGowwwRouter_StaticAll 28071 42629 ns/op 0 B/op 0 allocs/op +BenchmarkHttpRouter_StaticAll 47672 24875 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_StaticAll 46770 25100 ns/op 0 B/op 0 allocs/op +BenchmarkKocha_StaticAll 61045 19494 ns/op 0 B/op 0 allocs/op +BenchmarkLARS_StaticAll 36103 32700 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_StaticAll 4261 430131 ns/op 115552 B/op 1256 allocs/op +BenchmarkMartini_StaticAll 481 2320157 ns/op 125444 B/op 1717 allocs/op +BenchmarkPat_StaticAll 325 3739521 ns/op 602832 B/op 12559 allocs/op +BenchmarkPossum_StaticAll 10000 203575 ns/op 65312 B/op 471 allocs/op +BenchmarkR2router_StaticAll 10000 110536 ns/op 22608 B/op 628 allocs/op +BenchmarkRivet_StaticAll 23344 51174 ns/op 0 B/op 0 allocs/op +BenchmarkTango_StaticAll 3596 340045 ns/op 39209 B/op 1256 allocs/op +BenchmarkTigerTonic_StaticAll 16784 71807 ns/op 7376 B/op 157 allocs/op +BenchmarkTraffic_StaticAll 350 3435155 ns/op 754862 B/op 14601 allocs/op +BenchmarkVulcan_StaticAll 5930 200284 ns/op 15386 B/op 471 allocs/op ``` ## Micro Benchmarks ``` -BenchmarkGin_Param 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 +BenchmarkGin_Param 8623915 139 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_Param 3976539 290 ns/op 32 B/op 1 allocs/op +BenchmarkAero_Param 8948976 133 ns/op 0 B/op 0 allocs/op +BenchmarkBear_Param 1000000 1277 ns/op 456 B/op 5 allocs/op +BenchmarkBeego_Param 889404 1785 ns/op 352 B/op 3 allocs/op +BenchmarkBone_Param 1000000 2219 ns/op 816 B/op 6 allocs/op +BenchmarkChi_Param 1000000 1386 ns/op 432 B/op 3 allocs/op +BenchmarkCloudyKitRouter_Param 18343244 61.2 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_Param 5637424 204 ns/op 32 B/op 1 allocs/op +BenchmarkEcho_Param 9540910 122 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_Param 1000000 1939 ns/op 648 B/op 8 allocs/op +BenchmarkGoji_Param 1283509 938 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_Param 331266 3554 ns/op 1328 B/op 11 allocs/op +BenchmarkGoJsonRest_Param 908851 2158 ns/op 649 B/op 13 allocs/op +BenchmarkGoRestful_Param 135781 9339 ns/op 4192 B/op 14 allocs/op +BenchmarkGorillaMux_Param 308407 3893 ns/op 1280 B/op 10 allocs/op +BenchmarkGowwwRouter_Param 1000000 1044 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_Param 6653476 162 ns/op 32 B/op 1 allocs/op +BenchmarkHttpTreeMux_Param 1361378 819 ns/op 352 B/op 3 allocs/op +BenchmarkKocha_Param 3084330 353 ns/op 56 B/op 3 allocs/op +BenchmarkLARS_Param 11502079 107 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_Param 439095 3750 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_Param 177099 7479 ns/op 1072 B/op 10 allocs/op +BenchmarkPat_Param 729747 2048 ns/op 536 B/op 11 allocs/op +BenchmarkPossum_Param 995989 1705 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_Param 1000000 1037 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_Param 4057065 271 ns/op 48 B/op 1 allocs/op +BenchmarkTango_Param 812029 1682 ns/op 248 B/op 8 allocs/op +BenchmarkTigerTonic_Param 450592 3358 ns/op 776 B/op 16 allocs/op +BenchmarkTraffic_Param 206390 5661 ns/op 1856 B/op 21 allocs/op +BenchmarkVulcan_Param 1441147 792 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_Param5 1891473 632 ns/op 160 B/op 1 allocs/op +BenchmarkAero_Param5 5191258 227 ns/op 0 B/op 0 allocs/op +BenchmarkBear_Param5 988882 1734 ns/op 501 B/op 5 allocs/op +BenchmarkBeego_Param5 625438 2132 ns/op 352 B/op 3 allocs/op +BenchmarkBone_Param5 622030 3061 ns/op 864 B/op 6 allocs/op +BenchmarkChi_Param5 1000000 1735 ns/op 432 B/op 3 allocs/op +BenchmarkCloudyKitRouter_Param5 5167868 225 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_Param5 2174550 550 ns/op 160 B/op 1 allocs/op +BenchmarkEcho_Param5 4272258 275 ns/op 0 B/op 0 allocs/op +BenchmarkGin_Param5 4190391 275 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_Param5 623739 3107 ns/op 920 B/op 11 allocs/op +BenchmarkGoji_Param5 1000000 1310 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_Param5 314694 3803 ns/op 1392 B/op 11 allocs/op +BenchmarkGoJsonRest_Param5 308203 4108 ns/op 1097 B/op 16 allocs/op +BenchmarkGoRestful_Param5 115048 9787 ns/op 4288 B/op 14 allocs/op +BenchmarkGorillaMux_Param5 180812 5658 ns/op 1344 B/op 10 allocs/op +BenchmarkGowwwRouter_Param5 1000000 1156 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_Param5 2395767 502 ns/op 160 B/op 1 allocs/op +BenchmarkHttpTreeMux_Param5 899263 2096 ns/op 576 B/op 6 allocs/op +BenchmarkKocha_Param5 1000000 1639 ns/op 440 B/op 10 allocs/op +BenchmarkLARS_Param5 5807994 203 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_Param5 272967 4087 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_Param5 120735 8886 ns/op 1232 B/op 11 allocs/op +BenchmarkPat_Param5 294714 4943 ns/op 888 B/op 29 allocs/op +BenchmarkPossum_Param5 1000000 1689 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_Param5 1000000 1319 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_Param5 1347289 883 ns/op 240 B/op 1 allocs/op +BenchmarkTango_Param5 617077 2091 ns/op 360 B/op 8 allocs/op +BenchmarkTigerTonic_Param5 113659 11212 ns/op 2279 B/op 39 allocs/op +BenchmarkTraffic_Param5 134148 9039 ns/op 2208 B/op 27 allocs/op +BenchmarkVulcan_Param5 1000000 1095 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_Param20 1000000 1838 ns/op 640 B/op 1 allocs/op +BenchmarkAero_Param20 17120668 66.1 ns/op 0 B/op 0 allocs/op +BenchmarkBear_Param20 205585 5332 ns/op 1665 B/op 5 allocs/op +BenchmarkBeego_Param20 230522 5382 ns/op 352 B/op 3 allocs/op +BenchmarkBone_Param20 167190 8076 ns/op 2031 B/op 6 allocs/op +BenchmarkChi_Param20 480528 3044 ns/op 432 B/op 3 allocs/op +BenchmarkCloudyKitRouter_Param20 1347794 872 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_Param20 1000000 1867 ns/op 640 B/op 1 allocs/op +BenchmarkEcho_Param20 1363526 897 ns/op 0 B/op 0 allocs/op +BenchmarkGin_Param20 1607217 748 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_Param20 97314 11671 ns/op 3795 B/op 15 allocs/op +BenchmarkGoji_Param20 289407 4220 ns/op 1246 B/op 2 allocs/op +BenchmarkGojiv2_Param20 245186 4869 ns/op 1632 B/op 11 allocs/op +BenchmarkGoJsonRest_Param20 78049 15725 ns/op 4485 B/op 20 allocs/op +BenchmarkGoRestful_Param20 66907 18031 ns/op 6716 B/op 18 allocs/op +BenchmarkGorillaMux_Param20 81866 12422 ns/op 3452 B/op 12 allocs/op +BenchmarkGowwwRouter_Param20 955983 1688 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_Param20 1000000 1629 ns/op 640 B/op 1 allocs/op +BenchmarkHttpTreeMux_Param20 108940 10241 ns/op 3195 B/op 10 allocs/op +BenchmarkKocha_Param20 197022 5488 ns/op 1808 B/op 27 allocs/op +BenchmarkLARS_Param20 2451241 490 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_Param20 106770 10788 ns/op 2923 B/op 12 allocs/op +BenchmarkMartini_Param20 69028 17112 ns/op 3596 B/op 13 allocs/op +BenchmarkPat_Param20 56275 21535 ns/op 4424 B/op 93 allocs/op +BenchmarkPossum_Param20 1000000 1705 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_Param20 172215 7099 ns/op 2283 B/op 7 allocs/op +BenchmarkRivet_Param20 447265 2987 ns/op 1024 B/op 1 allocs/op +BenchmarkTango_Param20 327494 3850 ns/op 856 B/op 8 allocs/op +BenchmarkTigerTonic_Param20 27176 44571 ns/op 9871 B/op 119 allocs/op +BenchmarkTraffic_Param20 38828 31025 ns/op 7856 B/op 47 allocs/op +BenchmarkVulcan_Param20 560442 1807 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_ParamWrite 2712150 442 ns/op 40 B/op 2 allocs/op +BenchmarkAero_ParamWrite 6392880 189 ns/op 0 B/op 0 allocs/op +BenchmarkBear_ParamWrite 1000000 1338 ns/op 456 B/op 5 allocs/op +BenchmarkBeego_ParamWrite 821431 1886 ns/op 360 B/op 4 allocs/op +BenchmarkBone_ParamWrite 913227 2350 ns/op 816 B/op 6 allocs/op +BenchmarkChi_ParamWrite 1000000 1427 ns/op 432 B/op 3 allocs/op +BenchmarkCloudyKitRouter_ParamWrite 18645724 60.9 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_ParamWrite 4394764 264 ns/op 32 B/op 1 allocs/op +BenchmarkEcho_ParamWrite 5288883 242 ns/op 8 B/op 1 allocs/op +BenchmarkGin_ParamWrite 4584932 253 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_ParamWrite 866242 2094 ns/op 656 B/op 9 allocs/op +BenchmarkGoji_ParamWrite 1201875 1004 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_ParamWrite 317766 3777 ns/op 1360 B/op 13 allocs/op +BenchmarkGoJsonRest_ParamWrite 380242 3447 ns/op 1128 B/op 18 allocs/op +BenchmarkGoRestful_ParamWrite 131046 9340 ns/op 4200 B/op 15 allocs/op +BenchmarkGorillaMux_ParamWrite 298428 3970 ns/op 1280 B/op 10 allocs/op +BenchmarkGowwwRouter_ParamWrite 655940 2744 ns/op 976 B/op 8 allocs/op +BenchmarkHttpRouter_ParamWrite 5237014 219 ns/op 32 B/op 1 allocs/op +BenchmarkHttpTreeMux_ParamWrite 1379904 853 ns/op 352 B/op 3 allocs/op +BenchmarkKocha_ParamWrite 2939042 400 ns/op 56 B/op 3 allocs/op +BenchmarkLARS_ParamWrite 6181642 199 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_ParamWrite 352497 4670 ns/op 1176 B/op 14 allocs/op +BenchmarkMartini_ParamWrite 138259 8543 ns/op 1176 B/op 14 allocs/op +BenchmarkPat_ParamWrite 552386 3262 ns/op 960 B/op 15 allocs/op +BenchmarkPossum_ParamWrite 1000000 1711 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_ParamWrite 1000000 1085 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_ParamWrite 2374513 489 ns/op 112 B/op 2 allocs/op +BenchmarkTango_ParamWrite 1443907 812 ns/op 136 B/op 4 allocs/op +BenchmarkTigerTonic_ParamWrite 324264 4874 ns/op 1216 B/op 21 allocs/op +BenchmarkTraffic_ParamWrite 170726 7155 ns/op 2280 B/op 25 allocs/op +BenchmarkVulcan_ParamWrite 1498888 776 ns/op 98 B/op 3 allocs/op + ``` ## GitHub ``` -BenchmarkGin_GithubStatic 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 +BenchmarkGin_GithubStatic 5866748 194 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_GithubStatic 5815826 205 ns/op 0 B/op 0 allocs/op +BenchmarkAero_GithubStatic 10822906 106 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GithubStatic 1678065 707 ns/op 120 B/op 3 allocs/op +BenchmarkBeego_GithubStatic 828814 1717 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GithubStatic 67484 18858 ns/op 2880 B/op 60 allocs/op +BenchmarkCloudyKitRouter_GithubStatic 10219297 115 ns/op 0 B/op 0 allocs/op +BenchmarkChi_GithubStatic 1000000 1348 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_GithubStatic 15220622 75.4 ns/op 0 B/op 0 allocs/op +BenchmarkEcho_GithubStatic 7255897 158 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GithubStatic 1000000 1198 ns/op 296 B/op 5 allocs/op +BenchmarkGoji_GithubStatic 3659361 320 ns/op 0 B/op 0 allocs/op +BenchmarkGojiv2_GithubStatic 402402 3384 ns/op 1312 B/op 10 allocs/op +BenchmarkGoRestful_GithubStatic 54592 22045 ns/op 4256 B/op 13 allocs/op +BenchmarkGoJsonRest_GithubStatic 801067 1673 ns/op 329 B/op 11 allocs/op +BenchmarkGorillaMux_GithubStatic 169690 8171 ns/op 976 B/op 9 allocs/op +BenchmarkGowwwRouter_GithubStatic 5372910 218 ns/op 0 B/op 0 allocs/op +BenchmarkHttpRouter_GithubStatic 10965576 103 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_GithubStatic 10505365 106 ns/op 0 B/op 0 allocs/op +BenchmarkKocha_GithubStatic 14153763 81.9 ns/op 0 B/op 0 allocs/op +BenchmarkLARS_GithubStatic 7874017 152 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GithubStatic 696940 2678 ns/op 736 B/op 8 allocs/op +BenchmarkMartini_GithubStatic 102384 12276 ns/op 768 B/op 9 allocs/op +BenchmarkPat_GithubStatic 69907 17437 ns/op 3648 B/op 76 allocs/op +BenchmarkPossum_GithubStatic 1000000 1262 ns/op 416 B/op 3 allocs/op +BenchmarkR2router_GithubStatic 1981592 614 ns/op 144 B/op 4 allocs/op +BenchmarkRivet_GithubStatic 6103872 196 ns/op 0 B/op 0 allocs/op +BenchmarkTango_GithubStatic 629551 2023 ns/op 248 B/op 8 allocs/op +BenchmarkTigerTonic_GithubStatic 2801569 424 ns/op 48 B/op 1 allocs/op +BenchmarkTraffic_GithubStatic 63716 18009 ns/op 4664 B/op 90 allocs/op +BenchmarkVulcan_GithubStatic 885640 1177 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_GithubParam 2016942 582 ns/op 96 B/op 1 allocs/op +BenchmarkAero_GithubParam 4009522 296 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GithubParam 1000000 1575 ns/op 496 B/op 5 allocs/op +BenchmarkBeego_GithubParam 796662 2038 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GithubParam 114823 10325 ns/op 1888 B/op 19 allocs/op +BenchmarkChi_GithubParam 1000000 1783 ns/op 432 B/op 3 allocs/op +BenchmarkCloudyKitRouter_GithubParam 3910996 303 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_GithubParam 2298172 521 ns/op 128 B/op 1 allocs/op +BenchmarkEcho_GithubParam 3336364 357 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GithubParam 2729161 439 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GithubParam 825784 2338 ns/op 712 B/op 9 allocs/op +BenchmarkGoji_GithubParam 933397 1559 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_GithubParam 253884 4335 ns/op 1408 B/op 13 allocs/op +BenchmarkGoJsonRest_GithubParam 575532 2967 ns/op 713 B/op 14 allocs/op +BenchmarkGoRestful_GithubParam 38160 30638 ns/op 4352 B/op 16 allocs/op +BenchmarkGorillaMux_GithubParam 94554 12035 ns/op 1296 B/op 10 allocs/op +BenchmarkGowwwRouter_GithubParam 1000000 1223 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_GithubParam 2562079 468 ns/op 96 B/op 1 allocs/op +BenchmarkHttpTreeMux_GithubParam 1000000 1386 ns/op 384 B/op 4 allocs/op +BenchmarkKocha_GithubParam 1573026 754 ns/op 128 B/op 5 allocs/op +BenchmarkLARS_GithubParam 4203394 282 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GithubParam 365078 4137 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_GithubParam 71608 15811 ns/op 1152 B/op 11 allocs/op +BenchmarkPat_GithubParam 92768 13297 ns/op 2408 B/op 48 allocs/op +BenchmarkPossum_GithubParam 1000000 1704 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_GithubParam 1000000 1120 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_GithubParam 1642794 720 ns/op 96 B/op 1 allocs/op +BenchmarkTango_GithubParam 574195 2345 ns/op 344 B/op 8 allocs/op +BenchmarkTigerTonic_GithubParam 272430 5493 ns/op 1176 B/op 22 allocs/op +BenchmarkTraffic_GithubParam 81914 15078 ns/op 2816 B/op 40 allocs/op +BenchmarkVulcan_GithubParam 581272 1902 ns/op 98 B/op 3 allocs/op + + +BenchmarkAce_GithubAll 10000 103571 ns/op 13792 B/op 167 allocs/op +BenchmarkAero_GithubAll 21366 55615 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GithubAll 5288 327648 ns/op 86448 B/op 943 allocs/op +BenchmarkBeego_GithubAll 3974 413453 ns/op 71456 B/op 609 allocs/op +BenchmarkBone_GithubAll 267 4450294 ns/op 720160 B/op 8620 allocs/op +BenchmarkChi_GithubAll 5067 358773 ns/op 87696 B/op 609 allocs/op +BenchmarkCloudyKitRouter_GithubAll 24210 49233 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_GithubAll 12508 95341 ns/op 20224 B/op 167 allocs/op +BenchmarkEcho_GithubAll 16353 73267 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GithubAll 15516 77716 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GithubAll 2908 466970 ns/op 131656 B/op 1686 allocs/op +BenchmarkGoji_GithubAll 1746 691392 ns/op 56112 B/op 334 allocs/op +BenchmarkGojiv2_GithubAll 954 1289604 ns/op 352720 B/op 4321 allocs/op +BenchmarkGoJsonRest_GithubAll 2013 599088 ns/op 134371 B/op 2737 allocs/op +BenchmarkGoRestful_GithubAll 223 5404307 ns/op 910144 B/op 2938 allocs/op +BenchmarkGorillaMux_GithubAll 202 5943565 ns/op 251650 B/op 1994 allocs/op +BenchmarkGowwwRouter_GithubAll 9009 227799 ns/op 72144 B/op 501 allocs/op +BenchmarkHttpRouter_GithubAll 14570 78718 ns/op 13792 B/op 167 allocs/op +BenchmarkHttpTreeMux_GithubAll 7226 242491 ns/op 65856 B/op 671 allocs/op +BenchmarkKocha_GithubAll 8282 159873 ns/op 23304 B/op 843 allocs/op +BenchmarkLARS_GithubAll 22711 52745 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GithubAll 2067 563117 ns/op 149409 B/op 1624 allocs/op +BenchmarkMartini_GithubAll 218 5455290 ns/op 226552 B/op 2325 allocs/op +BenchmarkPat_GithubAll 174 6801582 ns/op 1483152 B/op 26963 allocs/op +BenchmarkPossum_GithubAll 8113 263665 ns/op 84448 B/op 609 allocs/op +BenchmarkR2router_GithubAll 7172 247198 ns/op 77328 B/op 979 allocs/op +BenchmarkRivet_GithubAll 10000 128086 ns/op 16272 B/op 167 allocs/op +BenchmarkTango_GithubAll 3316 472753 ns/op 63825 B/op 1618 allocs/op +BenchmarkTigerTonic_GithubAll 1176 1041991 ns/op 193856 B/op 4474 allocs/op +BenchmarkTraffic_GithubAll 226 5312082 ns/op 820742 B/op 14114 allocs/op +BenchmarkVulcan_GithubAll 3904 304440 ns/op 19894 B/op 609 allocs/op ``` ## Google+ ``` -BenchmarkGin_GPlusStatic 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 +BenchmarkGin_GPlusStatic 9172405 124 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_GPlusStatic 7784710 152 ns/op 0 B/op 0 allocs/op +BenchmarkAero_GPlusStatic 12771894 89.2 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GPlusStatic 2351325 512 ns/op 104 B/op 3 allocs/op +BenchmarkBeego_GPlusStatic 1000000 1643 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GPlusStatic 4419217 263 ns/op 32 B/op 1 allocs/op +BenchmarkChi_GPlusStatic 1000000 1282 ns/op 432 B/op 3 allocs/op +BenchmarkCloudyKitRouter_GPlusStatic 17730754 61.9 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_GPlusStatic 29549895 38.3 ns/op 0 B/op 0 allocs/op +BenchmarkEcho_GPlusStatic 10521789 111 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GPlusStatic 1000000 1053 ns/op 280 B/op 5 allocs/op +BenchmarkGoji_GPlusStatic 5209968 228 ns/op 0 B/op 0 allocs/op +BenchmarkGojiv2_GPlusStatic 306363 3348 ns/op 1312 B/op 10 allocs/op +BenchmarkGoJsonRest_GPlusStatic 1000000 1424 ns/op 329 B/op 11 allocs/op +BenchmarkGoRestful_GPlusStatic 130754 8760 ns/op 3872 B/op 13 allocs/op +BenchmarkGorillaMux_GPlusStatic 496250 2860 ns/op 976 B/op 9 allocs/op +BenchmarkGowwwRouter_GPlusStatic 16401519 66.5 ns/op 0 B/op 0 allocs/op +BenchmarkHttpRouter_GPlusStatic 21323139 50.3 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_GPlusStatic 14877926 68.7 ns/op 0 B/op 0 allocs/op +BenchmarkKocha_GPlusStatic 18375128 57.6 ns/op 0 B/op 0 allocs/op +BenchmarkLARS_GPlusStatic 11153810 101 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GPlusStatic 652598 2720 ns/op 736 B/op 8 allocs/op +BenchmarkMartini_GPlusStatic 218824 6532 ns/op 768 B/op 9 allocs/op +BenchmarkPat_GPlusStatic 2825560 428 ns/op 96 B/op 2 allocs/op +BenchmarkPossum_GPlusStatic 1000000 1236 ns/op 416 B/op 3 allocs/op +BenchmarkR2router_GPlusStatic 2222193 541 ns/op 144 B/op 4 allocs/op +BenchmarkRivet_GPlusStatic 9802023 114 ns/op 0 B/op 0 allocs/op +BenchmarkTango_GPlusStatic 980658 1465 ns/op 200 B/op 8 allocs/op +BenchmarkTigerTonic_GPlusStatic 4882701 239 ns/op 32 B/op 1 allocs/op +BenchmarkTraffic_GPlusStatic 508060 3465 ns/op 1112 B/op 16 allocs/op +BenchmarkVulcan_GPlusStatic 1608979 725 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_GPlusParam 2962957 414 ns/op 64 B/op 1 allocs/op +BenchmarkAero_GPlusParam 5667668 202 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GPlusParam 1000000 1271 ns/op 480 B/op 5 allocs/op +BenchmarkBeego_GPlusParam 869858 1874 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GPlusParam 869476 2395 ns/op 816 B/op 6 allocs/op +BenchmarkChi_GPlusParam 1000000 1469 ns/op 432 B/op 3 allocs/op +BenchmarkCloudyKitRouter_GPlusParam 11149783 108 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_GPlusParam 4007298 301 ns/op 64 B/op 1 allocs/op +BenchmarkEcho_GPlusParam 6448201 174 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GPlusParam 5470827 218 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GPlusParam 1000000 1939 ns/op 648 B/op 8 allocs/op +BenchmarkGoji_GPlusParam 1207621 997 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_GPlusParam 271326 4013 ns/op 1328 B/op 11 allocs/op +BenchmarkGoJsonRest_GPlusParam 781062 2303 ns/op 649 B/op 13 allocs/op +BenchmarkGoRestful_GPlusParam 121267 9871 ns/op 4192 B/op 14 allocs/op +BenchmarkGorillaMux_GPlusParam 228406 5156 ns/op 1280 B/op 10 allocs/op +BenchmarkGowwwRouter_GPlusParam 1000000 1074 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_GPlusParam 4399740 276 ns/op 64 B/op 1 allocs/op +BenchmarkHttpTreeMux_GPlusParam 1309540 898 ns/op 352 B/op 3 allocs/op +BenchmarkKocha_GPlusParam 2930965 403 ns/op 56 B/op 3 allocs/op +BenchmarkLARS_GPlusParam 7588237 151 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GPlusParam 434997 4195 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_GPlusParam 148207 8144 ns/op 1072 B/op 10 allocs/op +BenchmarkPat_GPlusParam 566829 2533 ns/op 576 B/op 11 allocs/op +BenchmarkPossum_GPlusParam 1000000 1723 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_GPlusParam 1000000 1100 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_GPlusParam 3309052 331 ns/op 48 B/op 1 allocs/op +BenchmarkTango_GPlusParam 693728 1825 ns/op 264 B/op 8 allocs/op +BenchmarkTigerTonic_GPlusParam 417693 3800 ns/op 856 B/op 16 allocs/op +BenchmarkTraffic_GPlusParam 179424 6641 ns/op 1872 B/op 21 allocs/op +BenchmarkVulcan_GPlusParam 1000000 1063 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_GPlus2Params 2720149 460 ns/op 64 B/op 1 allocs/op +BenchmarkAero_GPlus2Params 3525165 343 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GPlus2Params 1000000 1502 ns/op 496 B/op 5 allocs/op +BenchmarkBeego_GPlus2Params 730123 2102 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GPlus2Params 253177 5583 ns/op 1168 B/op 10 allocs/op +BenchmarkChi_GPlus2Params 1000000 1531 ns/op 432 B/op 3 allocs/op +BenchmarkCloudyKitRouter_GPlus2Params 6943176 168 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_GPlus2Params 2912601 413 ns/op 64 B/op 1 allocs/op +BenchmarkEcho_GPlus2Params 4149189 278 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GPlus2Params 3271269 356 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GPlus2Params 915531 2321 ns/op 712 B/op 9 allocs/op +BenchmarkGoji_GPlus2Params 1000000 1413 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_GPlus2Params 256640 4521 ns/op 1408 B/op 14 allocs/op +BenchmarkGoJsonRest_GPlus2Params 499140 3076 ns/op 713 B/op 14 allocs/op +BenchmarkGoRestful_GPlus2Params 105928 10148 ns/op 4384 B/op 16 allocs/op +BenchmarkGorillaMux_GPlus2Params 110953 9682 ns/op 1296 B/op 10 allocs/op +BenchmarkGowwwRouter_GPlus2Params 1000000 1112 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_GPlus2Params 3491893 321 ns/op 64 B/op 1 allocs/op +BenchmarkHttpTreeMux_GPlus2Params 1000000 1341 ns/op 384 B/op 4 allocs/op +BenchmarkKocha_GPlus2Params 1445288 790 ns/op 128 B/op 5 allocs/op +BenchmarkLARS_GPlus2Params 6644953 185 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GPlus2Params 424291 4321 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_GPlus2Params 70866 16407 ns/op 1200 B/op 13 allocs/op +BenchmarkPat_GPlus2Params 121308 10221 ns/op 2168 B/op 33 allocs/op +BenchmarkPossum_GPlus2Params 1000000 1847 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_GPlus2Params 1000000 1267 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_GPlus2Params 2017526 590 ns/op 96 B/op 1 allocs/op +BenchmarkTango_GPlus2Params 846003 2143 ns/op 344 B/op 8 allocs/op +BenchmarkTigerTonic_GPlus2Params 303597 5736 ns/op 1200 B/op 22 allocs/op +BenchmarkTraffic_GPlus2Params 95032 12817 ns/op 2248 B/op 28 allocs/op +BenchmarkVulcan_GPlus2Params 692610 1575 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_GPlusAll 271720 4948 ns/op 640 B/op 11 allocs/op +BenchmarkAero_GPlusAll 367956 2926 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GPlusAll 68161 17883 ns/op 5488 B/op 61 allocs/op +BenchmarkBeego_GPlusAll 46634 25369 ns/op 4576 B/op 39 allocs/op +BenchmarkBone_GPlusAll 24628 49198 ns/op 11744 B/op 109 allocs/op +BenchmarkChi_GPlusAll 60778 19356 ns/op 5616 B/op 39 allocs/op +BenchmarkCloudyKitRouter_GPlusAll 706952 1693 ns/op 0 B/op 0 allocs/op +BenchmarkDenco_GPlusAll 327422 4222 ns/op 672 B/op 11 allocs/op +BenchmarkEcho_GPlusAll 331987 3176 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GPlusAll 289526 3559 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GPlusAll 45805 26768 ns/op 8040 B/op 103 allocs/op +BenchmarkGoji_GPlusAll 74786 14428 ns/op 3696 B/op 22 allocs/op +BenchmarkGojiv2_GPlusAll 23822 50355 ns/op 17616 B/op 154 allocs/op +BenchmarkGoJsonRest_GPlusAll 35280 32989 ns/op 8117 B/op 170 allocs/op +BenchmarkGoRestful_GPlusAll 10000 129418 ns/op 55520 B/op 192 allocs/op +BenchmarkGorillaMux_GPlusAll 15968 76492 ns/op 16112 B/op 128 allocs/op +BenchmarkGowwwRouter_GPlusAll 100096 12644 ns/op 4752 B/op 33 allocs/op +BenchmarkHttpRouter_GPlusAll 474584 3704 ns/op 640 B/op 11 allocs/op +BenchmarkHttpTreeMux_GPlusAll 98506 12480 ns/op 4032 B/op 38 allocs/op +BenchmarkKocha_GPlusAll 213709 7358 ns/op 976 B/op 43 allocs/op +BenchmarkLARS_GPlusAll 466608 2363 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GPlusAll 34136 35790 ns/op 9568 B/op 104 allocs/op +BenchmarkMartini_GPlusAll 8911 124543 ns/op 14016 B/op 145 allocs/op +BenchmarkPat_GPlusAll 17391 69198 ns/op 15264 B/op 271 allocs/op +BenchmarkPossum_GPlusAll 66774 17004 ns/op 5408 B/op 39 allocs/op +BenchmarkR2router_GPlusAll 79681 13996 ns/op 5040 B/op 63 allocs/op +BenchmarkRivet_GPlusAll 258788 5344 ns/op 768 B/op 11 allocs/op +BenchmarkTango_GPlusAll 46930 25591 ns/op 3656 B/op 104 allocs/op +BenchmarkTigerTonic_GPlusAll 20768 58038 ns/op 11600 B/op 242 allocs/op +BenchmarkTraffic_GPlusAll 10000 108031 ns/op 26248 B/op 341 allocs/op +BenchmarkVulcan_GPlusAll 71826 15724 ns/op 1274 B/op 39 allocs/op ``` ## Parse.com ``` -BenchmarkGin_ParseStatic 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 +BenchmarkGin_ParseStatic 8683893 140 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_ParseStatic 7255582 160 ns/op 0 B/op 0 allocs/op +BenchmarkAero_ParseStatic 11960128 95.0 ns/op 0 B/op 0 allocs/op +BenchmarkBear_ParseStatic 1791033 659 ns/op 120 B/op 3 allocs/op +BenchmarkBeego_ParseStatic 937918 1688 ns/op 352 B/op 3 allocs/op +BenchmarkBone_ParseStatic 1261682 949 ns/op 144 B/op 3 allocs/op +BenchmarkChi_ParseStatic 1000000 1303 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_ParseStatic 23731242 49.8 ns/op 0 B/op 0 allocs/op +BenchmarkEcho_ParseStatic 10585060 116 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_ParseStatic 1000000 1156 ns/op 296 B/op 5 allocs/op +BenchmarkGoji_ParseStatic 3927530 300 ns/op 0 B/op 0 allocs/op +BenchmarkGojiv2_ParseStatic 474836 3281 ns/op 1312 B/op 10 allocs/op +BenchmarkGoJsonRest_ParseStatic 1000000 1445 ns/op 329 B/op 11 allocs/op +BenchmarkGoRestful_ParseStatic 101262 11612 ns/op 4256 B/op 13 allocs/op +BenchmarkGorillaMux_ParseStatic 562705 3530 ns/op 976 B/op 9 allocs/op +BenchmarkGowwwRouter_ParseStatic 16479007 69.5 ns/op 0 B/op 0 allocs/op +BenchmarkHttpRouter_ParseStatic 23205590 51.5 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_ParseStatic 10763127 106 ns/op 0 B/op 0 allocs/op +BenchmarkKocha_ParseStatic 17850259 60.9 ns/op 0 B/op 0 allocs/op +BenchmarkLARS_ParseStatic 10727432 108 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_ParseStatic 685586 2665 ns/op 736 B/op 8 allocs/op +BenchmarkMartini_ParseStatic 200642 7158 ns/op 768 B/op 9 allocs/op +BenchmarkPat_ParseStatic 1000000 1139 ns/op 240 B/op 5 allocs/op +BenchmarkPossum_ParseStatic 1000000 1241 ns/op 416 B/op 3 allocs/op +BenchmarkR2router_ParseStatic 2035426 597 ns/op 144 B/op 4 allocs/op +BenchmarkRivet_ParseStatic 9707011 127 ns/op 0 B/op 0 allocs/op +BenchmarkTango_ParseStatic 910617 1693 ns/op 248 B/op 8 allocs/op +BenchmarkTigerTonic_ParseStatic 3168885 385 ns/op 48 B/op 1 allocs/op +BenchmarkTraffic_ParseStatic 493339 4264 ns/op 1256 B/op 19 allocs/op +BenchmarkVulcan_ParseStatic 1394142 848 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_ParseParam 3106903 387 ns/op 64 B/op 1 allocs/op +BenchmarkAero_ParseParam 8045266 141 ns/op 0 B/op 0 allocs/op +BenchmarkBear_ParseParam 1000000 1434 ns/op 467 B/op 5 allocs/op +BenchmarkBeego_ParseParam 951460 1937 ns/op 352 B/op 3 allocs/op +BenchmarkBone_ParseParam 855555 2776 ns/op 896 B/op 7 allocs/op +BenchmarkChi_ParseParam 1000000 1457 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_ParseParam 4084116 301 ns/op 64 B/op 1 allocs/op +BenchmarkEcho_ParseParam 8440170 142 ns/op 0 B/op 0 allocs/op +BenchmarkGin_ParseParam 7716948 157 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_ParseParam 886284 2045 ns/op 664 B/op 8 allocs/op +BenchmarkGoji_ParseParam 1000000 1167 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_ParseParam 269731 3945 ns/op 1360 B/op 12 allocs/op +BenchmarkGoJsonRest_ParseParam 719587 2277 ns/op 649 B/op 13 allocs/op +BenchmarkGoRestful_ParseParam 96408 11925 ns/op 4576 B/op 14 allocs/op +BenchmarkGorillaMux_ParseParam 289303 4154 ns/op 1280 B/op 10 allocs/op +BenchmarkGowwwRouter_ParseParam 1000000 1070 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_ParseParam 4917758 232 ns/op 64 B/op 1 allocs/op +BenchmarkHttpTreeMux_ParseParam 1445443 828 ns/op 352 B/op 3 allocs/op +BenchmarkKocha_ParseParam 3116233 382 ns/op 56 B/op 3 allocs/op +BenchmarkLARS_ParseParam 10584750 113 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_ParseParam 413617 3872 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_ParseParam 166545 7605 ns/op 1072 B/op 10 allocs/op +BenchmarkPat_ParseParam 491829 3394 ns/op 992 B/op 15 allocs/op +BenchmarkPossum_ParseParam 1000000 1692 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_ParseParam 1000000 1059 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_ParseParam 3572359 311 ns/op 48 B/op 1 allocs/op +BenchmarkTango_ParseParam 787552 1889 ns/op 280 B/op 8 allocs/op +BenchmarkTigerTonic_ParseParam 487208 3706 ns/op 784 B/op 15 allocs/op +BenchmarkTraffic_ParseParam 186190 5812 ns/op 1896 B/op 21 allocs/op +BenchmarkVulcan_ParseParam 1275432 892 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_Parse2Params 2959621 412 ns/op 64 B/op 1 allocs/op +BenchmarkAero_Parse2Params 6208641 192 ns/op 0 B/op 0 allocs/op +BenchmarkBear_Parse2Params 1000000 1512 ns/op 496 B/op 5 allocs/op +BenchmarkBeego_Parse2Params 761940 1973 ns/op 352 B/op 3 allocs/op +BenchmarkBone_Parse2Params 715987 2582 ns/op 848 B/op 6 allocs/op +BenchmarkChi_Parse2Params 1000000 1495 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_Parse2Params 3585452 341 ns/op 64 B/op 1 allocs/op +BenchmarkEcho_Parse2Params 5193693 204 ns/op 0 B/op 0 allocs/op +BenchmarkGin_Parse2Params 5338316 236 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_Parse2Params 939637 2299 ns/op 712 B/op 9 allocs/op +BenchmarkGoji_Parse2Params 1000000 1094 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_Parse2Params 339514 3733 ns/op 1344 B/op 11 allocs/op +BenchmarkGoJsonRest_Parse2Params 512572 2733 ns/op 713 B/op 14 allocs/op +BenchmarkGoRestful_Parse2Params 95913 12973 ns/op 4928 B/op 14 allocs/op +BenchmarkGorillaMux_Parse2Params 261208 4758 ns/op 1296 B/op 10 allocs/op +BenchmarkGowwwRouter_Parse2Params 1000000 1084 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_Parse2Params 4399953 277 ns/op 64 B/op 1 allocs/op +BenchmarkHttpTreeMux_Parse2Params 1000000 1198 ns/op 384 B/op 4 allocs/op +BenchmarkKocha_Parse2Params 1669431 683 ns/op 128 B/op 5 allocs/op +BenchmarkLARS_Parse2Params 8535754 142 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_Parse2Params 424590 3959 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_Parse2Params 162448 8141 ns/op 1152 B/op 11 allocs/op +BenchmarkPat_Parse2Params 431336 3484 ns/op 752 B/op 16 allocs/op +BenchmarkPossum_Parse2Params 1000000 1721 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_Parse2Params 1000000 1136 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_Parse2Params 2630935 442 ns/op 96 B/op 1 allocs/op +BenchmarkTango_Parse2Params 759218 1876 ns/op 312 B/op 8 allocs/op +BenchmarkTigerTonic_Parse2Params 290810 5558 ns/op 1168 B/op 22 allocs/op +BenchmarkTraffic_Parse2Params 181099 6917 ns/op 1944 B/op 22 allocs/op +BenchmarkVulcan_Parse2Params 1000000 1080 ns/op 98 B/op 3 allocs/op + +BenchmarkAce_ParseAll 162906 7888 ns/op 640 B/op 16 allocs/op +BenchmarkAero_ParseAll 219260 4833 ns/op 0 B/op 0 allocs/op +BenchmarkBear_ParseAll 37566 32863 ns/op 8928 B/op 110 allocs/op +BenchmarkBeego_ParseAll 25400 46518 ns/op 9152 B/op 78 allocs/op +BenchmarkBone_ParseAll 19568 61814 ns/op 16208 B/op 147 allocs/op +BenchmarkChi_ParseAll 30562 38281 ns/op 11232 B/op 78 allocs/op +BenchmarkDenco_ParseAll 232554 6371 ns/op 928 B/op 16 allocs/op +BenchmarkEcho_ParseAll 224400 5090 ns/op 0 B/op 0 allocs/op +BenchmarkGin_ParseAll 189829 6134 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_ParseAll 25446 47000 ns/op 13728 B/op 181 allocs/op +BenchmarkGoji_ParseAll 50503 22949 ns/op 5376 B/op 32 allocs/op +BenchmarkGojiv2_ParseAll 12806 93106 ns/op 34448 B/op 277 allocs/op +BenchmarkGoJsonRest_ParseAll 20764 57021 ns/op 13866 B/op 321 allocs/op +BenchmarkGoRestful_ParseAll 4234 317238 ns/op 117600 B/op 354 allocs/op +BenchmarkGorillaMux_ParseAll 10000 146942 ns/op 30288 B/op 250 allocs/op +BenchmarkGowwwRouter_ParseAll 62548 19363 ns/op 6912 B/op 48 allocs/op +BenchmarkHttpRouter_ParseAll 286662 5091 ns/op 640 B/op 16 allocs/op +BenchmarkHttpTreeMux_ParseAll 66952 18262 ns/op 5728 B/op 51 allocs/op +BenchmarkKocha_ParseAll 109771 9811 ns/op 1112 B/op 54 allocs/op +BenchmarkLARS_ParseAll 272516 3976 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_ParseAll 17094 71634 ns/op 19136 B/op 208 allocs/op +BenchmarkMartini_ParseAll 6799 208122 ns/op 25072 B/op 253 allocs/op +BenchmarkPat_ParseAll 15993 74594 ns/op 15216 B/op 308 allocs/op +BenchmarkPossum_ParseAll 34897 33398 ns/op 10816 B/op 78 allocs/op +BenchmarkR2router_ParseAll 46909 25410 ns/op 8352 B/op 120 allocs/op +BenchmarkRivet_ParseAll 185193 7725 ns/op 912 B/op 16 allocs/op +BenchmarkTango_ParseAll 24481 47963 ns/op 7168 B/op 208 allocs/op +BenchmarkTigerTonic_ParseAll 15236 79623 ns/op 16048 B/op 332 allocs/op +BenchmarkTraffic_ParseAll 8955 169411 ns/op 45520 B/op 605 allocs/op +BenchmarkVulcan_ParseAll 40406 28971 ns/op 2548 B/op 78 allocs/op ``` From 3abc96e3cdc4e74daf52fff5fe7eb36b9674904b Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 1 Dec 2019 19:53:03 +0800 Subject: [PATCH 423/912] tree: sync part httprouter codes and reduce if/else (#2163) --- tree.go | 354 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 180 insertions(+), 174 deletions(-) diff --git a/tree.go b/tree.go index 4194757..1187f3f 100644 --- a/tree.go +++ b/tree.go @@ -62,6 +62,15 @@ func min(a, b int) int { return b } +func longestCommonPrefix(a, b string) int { + i := 0 + max := min(len(a), len(b)) + for i < max && a[i] == b[i] { + i++ + } + return i +} + func countParams(path string) uint8 { var n uint for i := 0; i < len(path); i++ { @@ -127,135 +136,132 @@ func (n *node) addRoute(path string, handlers HandlersChain) { n.priority++ numParams := countParams(path) + // Empty tree + if len(n.path) == 0 && len(n.children) == 0 { + n.insertChild(numParams, path, fullPath, handlers) + n.nType = root + return + } + parentFullPathIndex := 0 - // non-empty tree - if len(n.path) > 0 || len(n.children) > 0 { - walk: - for { - // Update maxParams of the current node - if numParams > n.maxParams { - n.maxParams = numParams - } +walk: + for { + // Update maxParams of the current node + if numParams > n.maxParams { + n.maxParams = numParams + } - // Find the longest common prefix. - // This also implies that the common prefix contains no ':' or '*' - // since the existing key can't contain those chars. - i := 0 - max := min(len(path), len(n.path)) - for i < max && path[i] == n.path[i] { - i++ + // Find the longest common prefix. + // This also implies that the common prefix contains no ':' or '*' + // since the existing key can't contain those chars. + i := longestCommonPrefix(path, n.path) + + // Split edge + if i < len(n.path) { + child := node{ + path: n.path[i:], + wildChild: n.wildChild, + indices: n.indices, + children: n.children, + handlers: n.handlers, + priority: n.priority - 1, + fullPath: n.fullPath, } - // Split edge - if i < len(n.path) { - child := node{ - path: n.path[i:], - wildChild: n.wildChild, - indices: n.indices, - children: n.children, - handlers: n.handlers, - priority: n.priority - 1, - fullPath: n.fullPath, - } - - // Update maxParams (max of all children) - for i := range child.children { - if child.children[i].maxParams > child.maxParams { - child.maxParams = child.children[i].maxParams - } + // Update maxParams (max of all children) + for i := range child.children { + if child.children[i].maxParams > child.maxParams { + child.maxParams = child.children[i].maxParams } - - n.children = []*node{&child} - // []byte for proper unicode char conversion, see #65 - n.indices = string([]byte{n.path[i]}) - n.path = path[:i] - n.handlers = nil - n.wildChild = false - n.fullPath = fullPath[:parentFullPathIndex+i] } - // Make new node a child of this node - if i < len(path) { - path = path[i:] + n.children = []*node{&child} + // []byte for proper unicode char conversion, see #65 + n.indices = string([]byte{n.path[i]}) + n.path = path[:i] + n.handlers = nil + n.wildChild = false + n.fullPath = fullPath[:parentFullPathIndex+i] + } - if n.wildChild { - parentFullPathIndex += len(n.path) - n = n.children[0] - n.priority++ + // Make new node a child of this node + if i < len(path) { + path = path[i:] - // Update maxParams of the child node - if numParams > n.maxParams { - n.maxParams = numParams - } - numParams-- + if n.wildChild { + parentFullPathIndex += len(n.path) + n = n.children[0] + n.priority++ - // Check if the wildcard matches - if len(path) >= len(n.path) && n.path == path[:len(n.path)] { - // check for longer wildcard, e.g. :name and :names - if len(n.path) >= len(path) || path[len(n.path)] == '/' { - continue walk - } - } + // Update maxParams of the child node + if numParams > n.maxParams { + n.maxParams = numParams + } + numParams-- - pathSeg := path - if n.nType != catchAll { - pathSeg = strings.SplitN(path, "/", 2)[0] + // Check if the wildcard matches + if len(path) >= len(n.path) && n.path == path[:len(n.path)] { + // check for longer wildcard, e.g. :name and :names + if len(n.path) >= len(path) || path[len(n.path)] == '/' { + continue walk } - prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path - panic("'" + pathSeg + - "' in new path '" + fullPath + - "' conflicts with existing wildcard '" + n.path + - "' in existing prefix '" + prefix + - "'") } - c := path[0] - - // slash after param - if n.nType == param && c == '/' && len(n.children) == 1 { - parentFullPathIndex += len(n.path) - n = n.children[0] - n.priority++ - continue walk + pathSeg := path + if n.nType != catchAll { + pathSeg = strings.SplitN(path, "/", 2)[0] } + prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path + panic("'" + pathSeg + + "' in new path '" + fullPath + + "' conflicts with existing wildcard '" + n.path + + "' in existing prefix '" + prefix + + "'") + } - // Check if a child with the next path byte exists - for i := 0; i < len(n.indices); i++ { - if c == n.indices[i] { - parentFullPathIndex += len(n.path) - i = n.incrementChildPrio(i) - n = n.children[i] - continue walk - } - } + c := path[0] - // Otherwise insert it - if c != ':' && c != '*' { - // []byte for proper unicode char conversion, see #65 - n.indices += string([]byte{c}) - child := &node{ - maxParams: numParams, - fullPath: fullPath, - } - n.children = append(n.children, child) - n.incrementChildPrio(len(n.indices) - 1) - n = child + // slash after param + if n.nType == param && c == '/' && len(n.children) == 1 { + parentFullPathIndex += len(n.path) + n = n.children[0] + n.priority++ + continue walk + } + + // Check if a child with the next path byte exists + for i := 0; i < len(n.indices); i++ { + if c == n.indices[i] { + parentFullPathIndex += len(n.path) + i = n.incrementChildPrio(i) + n = n.children[i] + continue walk } - n.insertChild(numParams, path, fullPath, handlers) - return + } - } else if i == len(path) { // Make node a (in-path) leaf - if n.handlers != nil { - panic("handlers are already registered for path '" + fullPath + "'") + // Otherwise insert it + if c != ':' && c != '*' { + // []byte for proper unicode char conversion, see #65 + n.indices += string([]byte{c}) + child := &node{ + maxParams: numParams, + fullPath: fullPath, } - n.handlers = handlers + n.children = append(n.children, child) + n.incrementChildPrio(len(n.indices) - 1) + n = child } + n.insertChild(numParams, path, fullPath, handlers) return + + } else if i == len(path) { // Make node a (in-path) leaf + if n.handlers != nil { + panic("handlers are already registered for path '" + fullPath + "'") + } + n.handlers = handlers } - } else { // Empty tree - n.insertChild(numParams, path, fullPath, handlers) - n.nType = root + return } } @@ -542,75 +548,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa path = path[len(n.path):] ciPath = append(ciPath, n.path...) - if len(path) > 0 { - // If this node does not have a wildcard (param or catchAll) child, - // we can just look up the next child node and continue to walk down - // the tree - if !n.wildChild { - r := unicode.ToLower(rune(path[0])) - for i, index := range n.indices { - // must use recursive approach since both index and - // ToLower(index) could exist. We must check both. - if r == unicode.ToLower(index) { - out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash) - if found { - return append(ciPath, out...), true - } - } - } - - // Nothing found. We can recommend to redirect to the same URL - // without a trailing slash if a leaf exists for that path - found = fixTrailingSlash && path == "/" && n.handlers != nil - return - } - - n = n.children[0] - switch n.nType { - case param: - // find param end (either '/' or path end) - k := 0 - for k < len(path) && path[k] != '/' { - k++ - } - - // add param value to case insensitive path - ciPath = append(ciPath, path[:k]...) - - // we need to go deeper! - if k < len(path) { - if len(n.children) > 0 { - path = path[k:] - n = n.children[0] - continue - } - - // ... but we can't - if fixTrailingSlash && len(path) == k+1 { - return ciPath, true - } - return - } - - if n.handlers != nil { - return ciPath, true - } else if fixTrailingSlash && len(n.children) == 1 { - // No handle found. Check if a handle for this path + a - // trailing slash exists - n = n.children[0] - if n.path == "/" && n.handlers != nil { - return append(ciPath, '/'), true - } - } - return - - case catchAll: - return append(ciPath, path...), true - - default: - panic("invalid node type") - } - } else { + if len(path) == 0 { // We should have reached the node containing the handle. // Check if this node has a handle registered. if n.handlers != nil { @@ -633,6 +571,74 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa } return } + + // If this node does not have a wildcard (param or catchAll) child, + // we can just look up the next child node and continue to walk down + // the tree + if !n.wildChild { + r := unicode.ToLower(rune(path[0])) + for i, index := range n.indices { + // must use recursive approach since both index and + // ToLower(index) could exist. We must check both. + if r == unicode.ToLower(index) { + out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash) + if found { + return append(ciPath, out...), true + } + } + } + + // Nothing found. We can recommend to redirect to the same URL + // without a trailing slash if a leaf exists for that path + found = fixTrailingSlash && path == "/" && n.handlers != nil + return + } + + n = n.children[0] + switch n.nType { + case param: + // find param end (either '/' or path end) + k := 0 + for k < len(path) && path[k] != '/' { + k++ + } + + // add param value to case insensitive path + ciPath = append(ciPath, path[:k]...) + + // we need to go deeper! + if k < len(path) { + if len(n.children) > 0 { + path = path[k:] + n = n.children[0] + continue + } + + // ... but we can't + if fixTrailingSlash && len(path) == k+1 { + return ciPath, true + } + return + } + + if n.handlers != nil { + return ciPath, true + } else if fixTrailingSlash && len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists + n = n.children[0] + if n.path == "/" && n.handlers != nil { + return append(ciPath, '/'), true + } + } + return + + case catchAll: + return append(ciPath, path...), true + + default: + panic("invalid node type") + } } // Nothing found. From 77b83441698ffcc400d02b9849f15d7a47e0b8bc Mon Sep 17 00:00:00 2001 From: Victor Castell Date: Mon, 2 Dec 2019 13:59:56 +0100 Subject: [PATCH 424/912] Add project to README (#2165) Add Dkron as user of Gin in the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 012152a..f8bc423 100644 --- a/README.md +++ b/README.md @@ -2096,3 +2096,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. +* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. From 7c21e04f628f1cabdf4029fd5c78a8f2b152faae Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 4 Dec 2019 07:56:01 +0800 Subject: [PATCH 425/912] fix maxParams bug (#2166) --- tree.go | 4 ++++ tree_test.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/tree.go b/tree.go index 1187f3f..b46ec82 100644 --- a/tree.go +++ b/tree.go @@ -357,6 +357,10 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle maxParams: 1, fullPath: fullPath, } + // update maxParams of the parent node + if n.maxParams < 1 { + n.maxParams = 1 + } n.children = []*node{child} n.indices = string(path[i]) n = child diff --git a/tree_test.go b/tree_test.go index e6e2886..0fe2fe0 100644 --- a/tree_test.go +++ b/tree_test.go @@ -368,6 +368,13 @@ func TestTreeCatchAllConflictRoot(t *testing.T) { testRoutes(t, routes) } +func TestTreeCatchMaxParams(t *testing.T) { + tree := &node{} + var route = "/cmd/*filepath" + tree.addRoute(route, fakeHandler(route)) + checkMaxParams(t, tree) +} + func TestTreeDoubleWildcard(t *testing.T) { const panicMsg = "only one wildcard per path segment is allowed" From c6544855d7244db15858cbc0bb200da5efa45b83 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 8 Dec 2019 18:35:08 +0800 Subject: [PATCH 426/912] tree: sync httprouter update (#2171) --- tree.go | 61 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/tree.go b/tree.go index b46ec82..ffd9989 100644 --- a/tree.go +++ b/tree.go @@ -107,16 +107,15 @@ type node struct { // increments priority of the given child and reorders if necessary. func (n *node) incrementChildPrio(pos int) int { - n.children[pos].priority++ - prio := n.children[pos].priority + cs := n.children + cs[pos].priority++ + prio := cs[pos].priority - // adjust position (move to front) + // Adjust position (move to front) newPos := pos - for newPos > 0 && n.children[newPos-1].priority < prio { - // swap node positions - n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1] - - newPos-- + for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- { + // Swap node positions + cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1] } // build new index char string @@ -231,7 +230,7 @@ walk: } // Check if a child with the next path byte exists - for i := 0; i < len(n.indices); i++ { + for i, max := 0, len(n.indices); i < max; i++ { if c == n.indices[i] { parentFullPathIndex += len(n.path) i = n.incrementChildPrio(i) @@ -404,17 +403,20 @@ func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) value.params = po walk: // Outer loop for walking the tree for { - if len(path) > len(n.path) { - if path[:len(n.path)] == n.path { - path = path[len(n.path):] + prefix := n.path + if len(path) > len(prefix) { + if path[:len(prefix)] == prefix { + path = path[len(prefix):] // If this node does not have a wildcard (param or catchAll) // child, we can just look up the next child node and continue // to walk down the tree if !n.wildChild { c := path[0] - for i := 0; i < len(n.indices); i++ { - if c == n.indices[i] { + indices := n.indices + for i, max := 0, len(indices); i < max; i++ { + if c == indices[i] { n = n.children[i] + prefix = n.path continue walk } } @@ -458,6 +460,7 @@ walk: // Outer loop for walking the tree if len(n.children) > 0 { path = path[end:] n = n.children[0] + prefix = n.path continue walk } @@ -504,7 +507,7 @@ walk: // Outer loop for walking the tree panic("invalid node type") } } - } else if path == n.path { + } else if path == prefix { // We should have reached the node containing the handle. // Check if this node has a handle registered. if value.handlers = n.handlers; value.handlers != nil { @@ -519,8 +522,9 @@ walk: // Outer loop for walking the tree // No handle found. Check if a handle for this path + a // trailing slash exists for trailing slash recommendation - for i := 0; i < len(n.indices); i++ { - if n.indices[i] == '/' { + indices := n.indices + for i, max := 0, len(indices); i < max; i++ { + if indices[i] == '/' { n = n.children[i] value.tsr = (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) @@ -534,8 +538,8 @@ walk: // Outer loop for walking the tree // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path value.tsr = (path == "/") || - (len(n.path) == len(path)+1 && n.path[len(path)] == '/' && - path == n.path[:len(n.path)-1] && n.handlers != nil) + (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && + path == prefix[:len(prefix)-1] && n.handlers != nil) return } } @@ -601,25 +605,25 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa n = n.children[0] switch n.nType { case param: - // find param end (either '/' or path end) - k := 0 - for k < len(path) && path[k] != '/' { - k++ + // Find param end (either '/' or path end) + end := 0 + for end < len(path) && path[end] != '/' { + end++ } // add param value to case insensitive path - ciPath = append(ciPath, path[:k]...) + ciPath = append(ciPath, path[:end]...) // we need to go deeper! - if k < len(path) { + if end < len(path) { if len(n.children) > 0 { - path = path[k:] + path = path[end:] n = n.children[0] continue } // ... but we can't - if fixTrailingSlash && len(path) == k+1 { + if fixTrailingSlash && len(path) == end+1 { return ciPath, true } return @@ -627,7 +631,8 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa if n.handlers != nil { return ciPath, true - } else if fixTrailingSlash && len(n.children) == 1 { + } + if fixTrailingSlash && len(n.children) == 1 { // No handle found. Check if a handle for this path + a // trailing slash exists n = n.children[0] From 6e16da8683136c68164b9011fc5678f46ad78d27 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 8 Dec 2019 19:34:05 +0800 Subject: [PATCH 427/912] tree: sync httprouter update (#2172) --- tree.go | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/tree.go b/tree.go index ffd9989..3a8a435 100644 --- a/tree.go +++ b/tree.go @@ -253,13 +253,13 @@ walk: } n.insertChild(numParams, path, fullPath, handlers) return + } - } else if i == len(path) { // Make node a (in-path) leaf - if n.handlers != nil { - panic("handlers are already registered for path '" + fullPath + "'") - } - n.handlers = handlers + // Otherwise and handle to current node + if n.handlers != nil { + panic("handlers are already registered for path '" + fullPath + "'") } + n.handlers = handlers return } } @@ -267,31 +267,31 @@ walk: func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { var offset int // already handled bytes of the path - // find prefix until first wildcard (beginning with ':' or '*') + // Find prefix until first wildcard (beginning with ':' or '*') for i, max := 0, len(path); numParams > 0; i++ { c := path[i] if c != ':' && c != '*' { continue } - // find wildcard end (either '/' or path end) + // Find wildcard end (either '/' or path end) and check the name for invalid characters end := i + 1 - for end < max && path[end] != '/' { - switch path[end] { - // the wildcard name must not contain ':' and '*' - case ':', '*': - panic("only one wildcard per path segment is allowed, has: '" + - path[i:] + "' in path '" + fullPath + "'") - default: - end++ + invalid := false + for end < max { + c := path[end] + if c == '/' { + break } + if c == ':' || c == '*' { + invalid = true + } + end++ } - // check if this Node existing children which would be - // unreachable if we insert the wildcard here - if len(n.children) > 0 { - panic("wildcard route '" + path[i:end] + - "' conflicts with existing children in path '" + fullPath + "'") + // The wildcard name must not contain ':' and '*' + if invalid { + panic("only one wildcard per path segment is allowed, has: '" + + path[i:end] + "' in path '" + fullPath + "'") } // check if the wildcard has a name @@ -299,6 +299,13 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") } + // Check if this node has existing children which would be + // unreachable if we insert the wildcard here + if len(n.children) > 0 { + panic("wildcard route '" + path[i:end] + + "' conflicts with existing children in path '" + fullPath + "'") + } + if c == ':' { // param // split path at the beginning of the wildcard if i > 0 { From 168fa945168119b7f72d5359f4abaf311e52f1b8 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 9 Dec 2019 15:04:35 +0800 Subject: [PATCH 428/912] tree: sync httprouter update (#2173) --- tree.go | 156 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 85 insertions(+), 71 deletions(-) diff --git a/tree.go b/tree.go index 3a8a435..b09d3f6 100644 --- a/tree.go +++ b/tree.go @@ -264,71 +264,80 @@ walk: } } -func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { - var offset int // already handled bytes of the path - - // Find prefix until first wildcard (beginning with ':' or '*') - for i, max := 0, len(path); numParams > 0; i++ { - c := path[i] +// Search for a wildcard segment and check the name for invalid characters. +// Returns -1 as index, if no wildcard war found. +func findWildcard(path string) (wildcard string, i int, valid bool) { + // Find start + for start, c := range []byte(path) { + // A wildcard starts with ':' (param) or '*' (catch-all) if c != ':' && c != '*' { continue } - // Find wildcard end (either '/' or path end) and check the name for invalid characters - end := i + 1 - invalid := false - for end < max { - c := path[end] - if c == '/' { - break - } - if c == ':' || c == '*' { - invalid = true + // Find end and check for invalid characters + valid = true + for end, c := range []byte(path[start+1:]) { + switch c { + case '/': + return path[start : start+1+end], start, valid + case ':', '*': + valid = false } - end++ + } + return path[start:], start, valid + } + return "", -1, false +} + +func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { + for numParams > 0 { + // Find prefix until first wildcard + wildcard, i, valid := findWildcard(path) + if i < 0 { // No wildcard found + break } // The wildcard name must not contain ':' and '*' - if invalid { + if !valid { panic("only one wildcard per path segment is allowed, has: '" + - path[i:end] + "' in path '" + fullPath + "'") + wildcard + "' in path '" + fullPath + "'") } // check if the wildcard has a name - if end-i < 2 { + if len(wildcard) < 2 { panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") } // Check if this node has existing children which would be // unreachable if we insert the wildcard here if len(n.children) > 0 { - panic("wildcard route '" + path[i:end] + + panic("wildcard segment '" + wildcard + "' conflicts with existing children in path '" + fullPath + "'") } - if c == ':' { // param - // split path at the beginning of the wildcard + if wildcard[0] == ':' { // param if i > 0 { - n.path = path[offset:i] - offset = i + // Insert prefix before the current wildcard + n.path = path[:i] + path = path[i:] } + n.wildChild = true child := &node{ nType: param, + path: wildcard, maxParams: numParams, fullPath: fullPath, } n.children = []*node{child} - n.wildChild = true n = child n.priority++ numParams-- // if the path doesn't end with the wildcard, then there // will be another non-wildcard subpath starting with '/' - if end < max { - n.path = path[offset:end] - offset = end + if len(wildcard) < len(path) { + path = path[len(wildcard):] child := &node{ maxParams: numParams, @@ -337,58 +346,63 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle } n.children = []*node{child} n = child + continue } - } else { // catchAll - if end != max || numParams > 1 { - panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") - } - - if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { - panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") - } + // Otherwise we're done. Insert the handle in the new leaf + n.handlers = handlers + return + } - // currently fixed width 1 for '/' - i-- - if path[i] != '/' { - panic("no / before catch-all in path '" + fullPath + "'") - } + // catchAll + if i+len(wildcard) != len(path) || numParams > 1 { + panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") + } - n.path = path[offset:i] + if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { + panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") + } - // first node: catchAll node with empty path - child := &node{ - wildChild: true, - nType: catchAll, - maxParams: 1, - fullPath: fullPath, - } - // update maxParams of the parent node - if n.maxParams < 1 { - n.maxParams = 1 - } - n.children = []*node{child} - n.indices = string(path[i]) - n = child - n.priority++ + // currently fixed width 1 for '/' + i-- + if path[i] != '/' { + panic("no / before catch-all in path '" + fullPath + "'") + } - // second node: node holding the variable - child = &node{ - path: path[i:], - nType: catchAll, - maxParams: 1, - handlers: handlers, - priority: 1, - fullPath: fullPath, - } - n.children = []*node{child} + n.path = path[:i] - return + // First node: catchAll node with empty path + child := &node{ + wildChild: true, + nType: catchAll, + maxParams: 1, + fullPath: fullPath, } + // update maxParams of the parent node + if n.maxParams < 1 { + n.maxParams = 1 + } + n.children = []*node{child} + n.indices = string('/') + n = child + n.priority++ + + // second node: node holding the variable + child = &node{ + path: path[i:], + nType: catchAll, + maxParams: 1, + handlers: handlers, + priority: 1, + fullPath: fullPath, + } + n.children = []*node{child} + + return } - // insert remaining path part and handle to the leaf - n.path = path[offset:] + // If no wildcard was found, simple insert the path and handle + n.path = path n.handlers = handlers n.fullPath = fullPath } From aee83e040b8f883ea98e3c1017d93db8ccc51c3d Mon Sep 17 00:00:00 2001 From: Lin Kao-Yuan Date: Wed, 18 Dec 2019 09:44:33 +0800 Subject: [PATCH 429/912] Fix "Custom Validators" example (#2186) * Update fixed error code from merged commit According to [this](https://github.com/gin-gonic/examples/commit/874dcfa6c457aa23996d67fa595a2acb8ea1f44b) merged commit. * Fixed incorrect testing date. Original testing date incompatible demo require, can't get expect result. check_in date need NOT AFTER check_out date. --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f8bc423..7e3e6f4 100644 --- a/README.md +++ b/README.md @@ -704,25 +704,22 @@ package main import ( "net/http" - "reflect" "time" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v9" ) // Booking contains binded and validated data. type Booking struct { - CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` + CheckIn time.Time `form:"check_in" binding:"required" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` } -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 { +var bookableDate validator.Func = func(fl validator.FieldLevel) bool { + date, ok := fl.Field().Interface().(time.Time) + if ok { today := time.Now() if today.After(date) { return false @@ -756,7 +753,7 @@ func getBookable(c *gin.Context) { $ 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" +$ curl "localhost:8085/bookable?check_in=2018-03-10&check_out=2018-03-09" {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} ``` From d6143d8d7c0c63d9bedc84f70c5d719f9dbf599b Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 18 Dec 2019 16:58:38 +0800 Subject: [PATCH 430/912] tree: remove one else statement (#2177) --- tree.go | 207 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 103 insertions(+), 104 deletions(-) diff --git a/tree.go b/tree.go index b09d3f6..89f74de 100644 --- a/tree.go +++ b/tree.go @@ -425,110 +425,7 @@ func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) walk: // Outer loop for walking the tree for { prefix := n.path - if len(path) > len(prefix) { - if path[:len(prefix)] == prefix { - path = path[len(prefix):] - // If this node does not have a wildcard (param or catchAll) - // child, we can just look up the next child node and continue - // to walk down the tree - if !n.wildChild { - c := path[0] - indices := n.indices - for i, max := 0, len(indices); i < max; i++ { - if c == indices[i] { - n = n.children[i] - prefix = n.path - continue walk - } - } - - // Nothing found. - // We can recommend to redirect to the same URL without a - // trailing slash if a leaf exists for that path. - value.tsr = path == "/" && n.handlers != nil - return - } - - // handle wildcard child - n = n.children[0] - switch n.nType { - case param: - // find param end (either '/' or path end) - end := 0 - for end < len(path) && path[end] != '/' { - end++ - } - - // save param value - if cap(value.params) < int(n.maxParams) { - value.params = make(Params, 0, n.maxParams) - } - i := len(value.params) - value.params = value.params[:i+1] // expand slice within preallocated capacity - value.params[i].Key = n.path[1:] - val := path[:end] - if unescape { - var err error - if value.params[i].Value, err = url.QueryUnescape(val); err != nil { - value.params[i].Value = val // fallback, in case of error - } - } else { - value.params[i].Value = val - } - - // we need to go deeper! - if end < len(path) { - if len(n.children) > 0 { - path = path[end:] - n = n.children[0] - prefix = n.path - continue walk - } - - // ... but we can't - value.tsr = len(path) == end+1 - return - } - - if value.handlers = n.handlers; value.handlers != nil { - value.fullPath = n.fullPath - return - } - if len(n.children) == 1 { - // No handle found. Check if a handle for this path + a - // trailing slash exists for TSR recommendation - n = n.children[0] - value.tsr = n.path == "/" && n.handlers != nil - } - - return - - case catchAll: - // save param value - if cap(value.params) < int(n.maxParams) { - value.params = make(Params, 0, n.maxParams) - } - i := len(value.params) - value.params = value.params[:i+1] // expand slice within preallocated capacity - value.params[i].Key = n.path[2:] - if unescape { - var err error - if value.params[i].Value, err = url.QueryUnescape(path); err != nil { - value.params[i].Value = path // fallback, in case of error - } - } else { - value.params[i].Value = path - } - - value.handlers = n.handlers - value.fullPath = n.fullPath - return - - default: - panic("invalid node type") - } - } - } else if path == prefix { + if path == prefix { // We should have reached the node containing the handle. // Check if this node has a handle registered. if value.handlers = n.handlers; value.handlers != nil { @@ -556,6 +453,108 @@ walk: // Outer loop for walking the tree return } + if len(path) > len(prefix) && path[:len(prefix)] == prefix { + path = path[len(prefix):] + // If this node does not have a wildcard (param or catchAll) + // child, we can just look up the next child node and continue + // to walk down the tree + if !n.wildChild { + c := path[0] + indices := n.indices + for i, max := 0, len(indices); i < max; i++ { + if c == indices[i] { + n = n.children[i] + prefix = n.path + continue walk + } + } + + // Nothing found. + // We can recommend to redirect to the same URL without a + // trailing slash if a leaf exists for that path. + value.tsr = path == "/" && n.handlers != nil + return + } + + // handle wildcard child + n = n.children[0] + switch n.nType { + case param: + // find param end (either '/' or path end) + end := 0 + for end < len(path) && path[end] != '/' { + end++ + } + + // save param value + if cap(value.params) < int(n.maxParams) { + value.params = make(Params, 0, n.maxParams) + } + i := len(value.params) + value.params = value.params[:i+1] // expand slice within preallocated capacity + value.params[i].Key = n.path[1:] + val := path[:end] + if unescape { + var err error + if value.params[i].Value, err = url.QueryUnescape(val); err != nil { + value.params[i].Value = val // fallback, in case of error + } + } else { + value.params[i].Value = val + } + + // we need to go deeper! + if end < len(path) { + if len(n.children) > 0 { + path = path[end:] + n = n.children[0] + prefix = n.path + continue walk + } + + // ... but we can't + value.tsr = len(path) == end+1 + return + } + + if value.handlers = n.handlers; value.handlers != nil { + value.fullPath = n.fullPath + return + } + if len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists for TSR recommendation + n = n.children[0] + value.tsr = n.path == "/" && n.handlers != nil + } + return + + case catchAll: + // save param value + if cap(value.params) < int(n.maxParams) { + value.params = make(Params, 0, n.maxParams) + } + i := len(value.params) + value.params = value.params[:i+1] // expand slice within preallocated capacity + value.params[i].Key = n.path[2:] + if unescape { + var err error + if value.params[i].Value, err = url.QueryUnescape(path); err != nil { + value.params[i].Value = path // fallback, in case of error + } + } else { + value.params[i].Value = path + } + + value.handlers = n.handlers + value.fullPath = n.fullPath + return + + default: + panic("invalid node type") + } + } + // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path value.tsr = (path == "/") || From 1b480ed294cb6d8727e95534af6e668a40231dbd Mon Sep 17 00:00:00 2001 From: Lin Kao-Yuan Date: Wed, 18 Dec 2019 21:08:58 +0800 Subject: [PATCH 431/912] Update to currently output (#2188) Excuse me, I forgot change output in #2186 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e3e6f4..01c4089 100644 --- a/README.md +++ b/README.md @@ -754,7 +754,7 @@ $ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17" {"message":"Booking dates are valid!"} $ curl "localhost:8085/bookable?check_in=2018-03-10&check_out=2018-03-09" -{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} +{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} ``` [Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. From cc14a770cd11fbd0d1aa1a0a895e69ce8cd1415a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=9E=E9=9B=AA=E6=97=A0=E6=83=85?= Date: Thu, 19 Dec 2019 11:21:58 +0800 Subject: [PATCH 432/912] upgrade go-validator to v10 for README (#2189) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01c4089..092fedc 100644 --- a/README.md +++ b/README.md @@ -584,7 +584,7 @@ func main() { To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). -Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags). +Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. From 9b3477ef9d2c6a611c9c00cc18aba5a6ba6a7641 Mon Sep 17 00:00:00 2001 From: Lin Kao-Yuan Date: Fri, 20 Dec 2019 14:01:58 +0800 Subject: [PATCH 433/912] Update validator to v10 (#2190) Passed my manual test, output nothing different. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 092fedc..5c77b28 100644 --- a/README.md +++ b/README.md @@ -708,7 +708,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "gopkg.in/go-playground/validator.v9" + "gopkg.in/go-playground/validator.v10" ) // Booking contains binded and validated data. From 59ab588bf597f9f41faee4f217b5659893c2e925 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Mon, 30 Dec 2019 23:55:08 +1000 Subject: [PATCH 434/912] Remove broken link from README. (#2198) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5c77b28..6e0ceb2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ## Contents - [Installation](#installation) -- [Prerequisite](#prerequisite) - [Quick start](#quick-start) - [Benchmarks](#benchmarks) - [Gin v1.stable](#gin-v1-stable) From b8a7b6d1945db03a70de86c5837caa93486d1f99 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Tue, 7 Jan 2020 11:19:49 +1000 Subject: [PATCH 435/912] Fix spelling (#2202) --- CHANGELOG.md | 4 ++-- context.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb90f2..1ceb919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -196,7 +196,7 @@ - [PERFORMANCE] Misc code optimizations. Inlining, tail call optimizations - [NEW] Built-in support for golang.org/x/net/context - [NEW] Any(path, handler). Create a route that matches any path -- [NEW] Refactored rendering pipeline (faster and static typeded) +- [NEW] Refactored rendering pipeline (faster and static typed) - [NEW] Refactored errors API - [NEW] IndentedJSON() prints pretty JSON - [NEW] Added gin.DefaultWriter @@ -295,7 +295,7 @@ - [FIX] Recovery() middleware only prints panics - [FIX] Context.Get() does not panic anymore. Use MustGet() instead. - [FIX] Multiple http.WriteHeader() in NotFound handlers -- [FIX] Engine.Run() panics if http server can't be setted up +- [FIX] Engine.Run() panics if http server can't be set up - [FIX] Crash when route path doesn't start with '/' - [FIX] Do not update header when status code is negative - [FIX] Setting response headers before calling WriteHeader in context.String() diff --git a/context.go b/context.go index 046f284..9be222f 100644 --- a/context.go +++ b/context.go @@ -1003,20 +1003,20 @@ func (c *Context) NegotiateFormat(offered ...string) string { return offered[0] } for _, accepted := range c.Accepted { - for _, offert := range offered { + for _, offer := range offered { // According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers, // therefore we can just iterate over the string without casting it into []rune i := 0 for ; i < len(accepted); i++ { - if accepted[i] == '*' || offert[i] == '*' { - return offert + if accepted[i] == '*' || offer[i] == '*' { + return offer } - if accepted[i] != offert[i] { + if accepted[i] != offer[i] { break } } if i == len(accepted) { - return offert + return offer } } } From fd8a65b2529cb17f1026b10872281f22911846ad Mon Sep 17 00:00:00 2001 From: Antoine GIRARD Date: Tue, 7 Jan 2020 04:31:10 +0100 Subject: [PATCH 436/912] Add build tag nomsgpack (#1852) * add build tag nomsgpack * Update copyright * Update copyright --- .travis.yml | 3 + Makefile | 3 +- binding/binding.go | 2 + binding/binding_msgpack_test.go | 57 ++++++++++++++++ binding/binding_nomsgpack.go | 111 ++++++++++++++++++++++++++++++++ binding/binding_test.go | 41 ------------ binding/msgpack.go | 2 + binding/msgpack_test.go | 2 + render/msgpack.go | 6 ++ render/render.go | 1 - render/render_msgpack_test.go | 43 +++++++++++++ render/render_test.go | 26 -------- 12 files changed, 228 insertions(+), 69 deletions(-) create mode 100644 binding/binding_msgpack_test.go create mode 100644 binding/binding_nomsgpack.go create mode 100644 render/render_msgpack_test.go diff --git a/.travis.yml b/.travis.yml index b80b257..582b732 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,9 @@ matrix: - go: 1.12.x env: GO111MODULE=on - go: 1.13.x + - go: 1.13.x + env: + - TESTTAGS=nomsgpack - go: master git: diff --git a/Makefile b/Makefile index e69dbd8..1a99193 100644 --- a/Makefile +++ b/Makefile @@ -4,12 +4,13 @@ PACKAGES ?= $(shell $(GO) list ./...) VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/) GOFILES := $(shell find . -name "*.go") TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples) +TESTTAGS ?= "" .PHONY: test test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ - $(GO) test -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + $(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ cat tmp.out; \ if grep -q "^--- FAIL" tmp.out; then \ rm tmp.out; \ diff --git a/binding/binding.go b/binding/binding.go index f578aa5..5756284 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +// +build !nomsgpack + package binding import "net/http" diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go new file mode 100644 index 0000000..9791a60 --- /dev/null +++ b/binding/binding_msgpack_test.go @@ -0,0 +1,57 @@ +// Copyright 2020 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !nomsgpack + +package binding + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" +) + +func TestBindingMsgPack(t *testing.T) { + test := FooStruct{ + Foo: "bar", + } + + h := new(codec.MsgpackHandle) + assert.NotNil(t, h) + buf := bytes.NewBuffer([]byte{}) + assert.NotNil(t, buf) + err := codec.NewEncoder(buf, h).Encode(test) + assert.NoError(t, err) + + data := buf.Bytes() + + testMsgPackBodyBinding(t, + MsgPack, "msgpack", + "/", "/", + string(data), string(data[1:])) +} + +func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, name, b.Name()) + + obj := FooStruct{} + req := requestWithBody("POST", path, body) + req.Header.Add("Content-Type", MIMEMSGPACK) + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, "bar", obj.Foo) + + obj = FooStruct{} + req = requestWithBody("POST", badPath, badBody) + req.Header.Add("Content-Type", MIMEMSGPACK) + err = MsgPack.Bind(req, &obj) + assert.Error(t, err) +} + +func TestBindingDefaultMsgPack(t *testing.T) { + assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) + assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) +} diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go new file mode 100644 index 0000000..fd227b1 --- /dev/null +++ b/binding/binding_nomsgpack.go @@ -0,0 +1,111 @@ +// Copyright 2020 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build nomsgpack + +package binding + +import "net/http" + +// Content-Type MIME of the most common data formats. +const ( + MIMEJSON = "application/json" + MIMEHTML = "text/html" + MIMEXML = "application/xml" + MIMEXML2 = "text/xml" + MIMEPlain = "text/plain" + MIMEPOSTForm = "application/x-www-form-urlencoded" + MIMEMultipartPOSTForm = "multipart/form-data" + MIMEPROTOBUF = "application/x-protobuf" + MIMEYAML = "application/x-yaml" +) + +// Binding describes the interface which needs to be implemented for binding the +// data present in the request such as JSON request body, query parameters or +// the form POST. +type Binding interface { + Name() string + Bind(*http.Request, interface{}) error +} + +// BindingBody adds BindBody method to Binding. BindBody is similar with Bind, +// but it reads the body from supplied bytes instead of req.Body. +type BindingBody interface { + Binding + BindBody([]byte, interface{}) error +} + +// BindingUri adds BindUri method to Binding. BindUri is similar with Bind, +// but it read the Params. +type BindingUri interface { + Name() string + BindUri(map[string][]string, interface{}) error +} + +// StructValidator is the minimal interface which needs to be implemented in +// order for it to be used as the validator engine for ensuring the correctness +// of the request. Gin provides a default implementation for this using +// https://github.com/go-playground/validator/tree/v8.18.2. +type StructValidator interface { + // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. + // If the received type is not a struct, any validation should be skipped and nil must be returned. + // If the received type is a struct or pointer to a struct, the validation should be performed. + // If the struct is not valid or the validation itself fails, a descriptive error should be returned. + // Otherwise nil must be returned. + ValidateStruct(interface{}) error + + // Engine returns the underlying validator engine which powers the + // StructValidator implementation. + Engine() interface{} +} + +// Validator is the default validator which implements the StructValidator +// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 +// under the hood. +var Validator StructValidator = &defaultValidator{} + +// These implement the Binding interface and can be used to bind the data +// present in the request to struct instances. +var ( + JSON = jsonBinding{} + XML = xmlBinding{} + Form = formBinding{} + Query = queryBinding{} + FormPost = formPostBinding{} + FormMultipart = formMultipartBinding{} + ProtoBuf = protobufBinding{} + YAML = yamlBinding{} + Uri = uriBinding{} + Header = headerBinding{} +) + +// Default returns the appropriate Binding instance based on the HTTP method +// and the content type. +func Default(method, contentType string) Binding { + if method == "GET" { + return Form + } + + switch contentType { + case MIMEJSON: + return JSON + case MIMEXML, MIMEXML2: + return XML + case MIMEPROTOBUF: + return ProtoBuf + case MIMEYAML: + return YAML + case MIMEMultipartPOSTForm: + return FormMultipart + default: // case MIMEPOSTForm: + return Form + } +} + +func validate(obj interface{}) error { + if Validator == nil { + return nil + } + return Validator.ValidateStruct(obj) +} diff --git a/binding/binding_test.go b/binding/binding_test.go index f0b6f79..4424bab 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -21,7 +21,6 @@ import ( "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" - "github.com/ugorji/go/codec" ) type appkey struct { @@ -163,9 +162,6 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) - assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) - assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) - assert.Equal(t, YAML, Default("POST", MIMEYAML)) assert.Equal(t, YAML, Default("PUT", MIMEYAML)) } @@ -633,26 +629,6 @@ func TestBindingProtoBufFail(t *testing.T) { string(data), string(data[1:])) } -func TestBindingMsgPack(t *testing.T) { - test := FooStruct{ - Foo: "bar", - } - - h := new(codec.MsgpackHandle) - assert.NotNil(t, h) - buf := bytes.NewBuffer([]byte{}) - assert.NotNil(t, buf) - err := codec.NewEncoder(buf, h).Encode(test) - assert.NoError(t, err) - - data := buf.Bytes() - - testMsgPackBodyBinding(t, - MsgPack, "msgpack", - "/", "/", - string(data), string(data[1:])) -} - func TestValidationFails(t *testing.T) { var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) @@ -1250,23 +1226,6 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body assert.Error(t, err) } -func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, name, b.Name()) - - obj := FooStruct{} - req := requestWithBody("POST", path, body) - req.Header.Add("Content-Type", MIMEMSGPACK) - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, "bar", obj.Foo) - - obj = FooStruct{} - req = requestWithBody("POST", badPath, badBody) - req.Header.Add("Content-Type", MIMEMSGPACK) - err = MsgPack.Bind(req, &obj) - assert.Error(t, err) -} - func requestWithBody(method, path, body string) (req *http.Request) { req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) return diff --git a/binding/msgpack.go b/binding/msgpack.go index b7f7319..a5bc2ad 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +// +build !nomsgpack + package binding import ( diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go index 6baa673..296d3eb 100644 --- a/binding/msgpack_test.go +++ b/binding/msgpack_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +// +build !nomsgpack + package binding import ( diff --git a/render/msgpack.go b/render/msgpack.go index dc681fc..be2d45c 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +// +build !nomsgpack + package render import ( @@ -10,6 +12,10 @@ import ( "github.com/ugorji/go/codec" ) +var ( + _ Render = MsgPack{} +) + // MsgPack contains the given interface object. type MsgPack struct { Data interface{} diff --git a/render/render.go b/render/render.go index abfc79f..bcd568b 100644 --- a/render/render.go +++ b/render/render.go @@ -27,7 +27,6 @@ var ( _ HTMLRender = HTMLDebug{} _ HTMLRender = HTMLProduction{} _ Render = YAML{} - _ Render = MsgPack{} _ Render = Reader{} _ Render = AsciiJSON{} _ Render = ProtoBuf{} diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go new file mode 100644 index 0000000..e439ac4 --- /dev/null +++ b/render/render_msgpack_test.go @@ -0,0 +1,43 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !nomsgpack + +package render + +import ( + "bytes" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" +) + +// TODO unit tests +// test errors + +func TestRenderMsgPack(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + + (MsgPack{data}).WriteContentType(w) + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) + + err := (MsgPack{data}).Render(w) + + assert.NoError(t, err) + + h := new(codec.MsgpackHandle) + assert.NotNil(t, h) + buf := bytes.NewBuffer([]byte{}) + assert.NotNil(t, buf) + err = codec.NewEncoder(buf, h).Encode(data) + + assert.NoError(t, err) + assert.Equal(t, w.Body.String(), string(buf.Bytes())) + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) +} diff --git a/render/render_test.go b/render/render_test.go index 376733d..d0b5615 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -5,7 +5,6 @@ package render import ( - "bytes" "encoding/xml" "errors" "html/template" @@ -17,7 +16,6 @@ import ( "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" - "github.com/ugorji/go/codec" testdata "github.com/gin-gonic/gin/testdata/protoexample" ) @@ -25,30 +23,6 @@ import ( // TODO unit tests // test errors -func TestRenderMsgPack(t *testing.T) { - w := httptest.NewRecorder() - data := map[string]interface{}{ - "foo": "bar", - } - - (MsgPack{data}).WriteContentType(w) - assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) - - err := (MsgPack{data}).Render(w) - - assert.NoError(t, err) - - h := new(codec.MsgpackHandle) - assert.NotNil(t, h) - buf := bytes.NewBuffer([]byte{}) - assert.NotNil(t, buf) - err = codec.NewEncoder(buf, h).Encode(data) - - assert.NoError(t, err) - assert.Equal(t, w.Body.String(), buf.String()) - assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) -} - func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() data := map[string]interface{}{ From 025950afe980be14531c51c2790dcb966da1d3fd Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Tue, 7 Jan 2020 17:37:18 +0800 Subject: [PATCH 437/912] Reuse bytes when cleaning the URL paths (#2179) * path: use stack buffer in CleanPath to avoid allocs in common case Sync from https://github.com/julienschmidt/httprouter/commit/8222db13dbb3b3ab1eb84edb61a7030708b93bfa * path: sync test code from httprouter * path: update path_test.go to the latest code Co-authored-by: Bo-Yi Wu --- path.go | 33 +++++++++++++++++++------- path_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 83 insertions(+), 15 deletions(-) diff --git a/path.go b/path.go index d1f5962..a6bac7b 100644 --- a/path.go +++ b/path.go @@ -5,6 +5,8 @@ package gin +const stackBufSize = 128 + // cleanPath is the URL version of path.Clean, it returns a canonical URL path // for p, eliminating . and .. elements. // @@ -24,8 +26,11 @@ func cleanPath(p string) string { return "/" } + // Reasonably sized buffer on stack to avoid allocations in the common case. + // If a larger buffer is required, it gets allocated dynamically. + buf := make([]byte, 0, stackBufSize) + n := len(p) - var buf []byte // Invariants: // reading from path; r is index of next byte to process. @@ -37,7 +42,12 @@ func cleanPath(p string) string { if p[0] != '/' { r = 0 - buf = make([]byte, n+1) + + if n+1 > stackBufSize { + buf = make([]byte, n+1) + } else { + buf = buf[:n+1] + } buf[0] = '/' } @@ -69,7 +79,7 @@ func cleanPath(p string) string { // can backtrack w-- - if buf == nil { + if len(buf) == 0 { for w > 1 && p[w] != '/' { w-- } @@ -103,7 +113,7 @@ func cleanPath(p string) string { w++ } - if buf == nil { + if len(buf) == 0 { return p[:w] } return string(buf[:w]) @@ -111,13 +121,20 @@ func cleanPath(p string) string { // internal helper to lazily create a buffer if necessary. func bufApp(buf *[]byte, s string, w int, c byte) { - if *buf == nil { + b := *buf + if len(b) == 0 { if s[w] == c { return } - *buf = make([]byte, len(s)) - copy(*buf, s[:w]) + if l := len(s); l > cap(b) { + *buf = make([]byte, len(s)) + } else { + *buf = (*buf)[:l] + } + b = *buf + + copy(b, s[:w]) } - (*buf)[w] = c + b[w] = c } diff --git a/path_test.go b/path_test.go index c1e6ed4..caefd63 100644 --- a/path_test.go +++ b/path_test.go @@ -6,15 +6,17 @@ package gin import ( - "runtime" + "strings" "testing" "github.com/stretchr/testify/assert" ) -var cleanTests = []struct { +type cleanPathTest struct { path, result string -}{ +} + +var cleanTests = []cleanPathTest{ // Already clean {"/", "/"}, {"/abc", "/abc"}, @@ -77,13 +79,62 @@ func TestPathCleanMallocs(t *testing.T) { if testing.Short() { t.Skip("skipping malloc count in short mode") } - if runtime.GOMAXPROCS(0) > 1 { - t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") - return - } for _, test := range cleanTests { allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) }) assert.EqualValues(t, allocs, 0) } } + +func BenchmarkPathClean(b *testing.B) { + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for _, test := range cleanTests { + cleanPath(test.path) + } + } +} + +func genLongPaths() (testPaths []cleanPathTest) { + for i := 1; i <= 1234; i++ { + ss := strings.Repeat("a", i) + + correctPath := "/" + ss + testPaths = append(testPaths, cleanPathTest{ + path: correctPath, + result: correctPath, + }, cleanPathTest{ + path: ss, + result: correctPath, + }, cleanPathTest{ + path: "//" + ss, + result: correctPath, + }, cleanPathTest{ + path: "/" + ss + "/b/..", + result: correctPath, + }) + } + return +} + +func TestPathCleanLong(t *testing.T) { + cleanTests := genLongPaths() + + for _, test := range cleanTests { + assert.Equal(t, test.result, cleanPath(test.path)) + assert.Equal(t, test.result, cleanPath(test.result)) + } +} + +func BenchmarkPathCleanLong(b *testing.B) { + cleanTests := genLongPaths() + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for _, test := range cleanTests { + cleanPath(test.path) + } + } +} From 424e9685bebad809ce5a0cb43d6511e79ad3a878 Mon Sep 17 00:00:00 2001 From: Andrey Abramov Date: Tue, 7 Jan 2020 20:48:28 +0300 Subject: [PATCH 438/912] Update docs on Context.Done(), Context.Deadline() and Context.Err() (#2196) Co-authored-by: Bo-Yi Wu --- context.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/context.go b/context.go index 9be222f..e979d28 100644 --- a/context.go +++ b/context.go @@ -1032,26 +1032,20 @@ func (c *Context) SetAccepted(formats ...string) { /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. +// Deadline always returns that there is no deadline (ok==false), +// maybe you want to use Request.Context().Deadline() instead. func (c *Context) Deadline() (deadline time.Time, ok bool) { return } -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. +// Done always returns nil (chan which will wait forever), +// if you want to abort your work when the connection was closed +// you should use Request.Context().Done() instead. func (c *Context) Done() <-chan struct{} { return nil } -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. +// Err always returns nil, maybe you want to use Request.Context().Err() instead. func (c *Context) Err() error { return nil } From ace6e4c2eac3acd6e6df75fa10fff17ee9319382 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Thu, 16 Jan 2020 22:40:59 +0800 Subject: [PATCH 439/912] path: sync code with httprouter (#2212) --- path.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/path.go b/path.go index a6bac7b..37247d4 100644 --- a/path.go +++ b/path.go @@ -5,8 +5,6 @@ package gin -const stackBufSize = 128 - // cleanPath is the URL version of path.Clean, it returns a canonical URL path // for p, eliminating . and .. elements. // @@ -21,6 +19,7 @@ const stackBufSize = 128 // // If the result of this process is an empty string, "/" is returned. func cleanPath(p string) string { + const stackBufSize = 128 // Turn empty string into "/" if p == "" { return "/" From 982daeb1ecdced87bbef6c11783a640eb88a193a Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Sat, 18 Jan 2020 00:32:50 +0800 Subject: [PATCH 440/912] =?UTF-8?q?Use=20zero-copy=20approach=20to=20conve?= =?UTF-8?q?rt=20types=20between=20string=20and=20byte=E2=80=A6=20(#2206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use zero-copy approach to convert types between string and byte slice * Rename argument to a eligible one Benchmark: BenchmarkBytesConvBytesToStrRaw-4 21003800 70.9 ns/op 96 B/op 1 allocs/op BenchmarkBytesConvBytesToStr-4 1000000000 0.333 ns/op 0 B/op 0 allocs/op BenchmarkBytesConvStrToBytesRaw-4 18478059 59.3 ns/op 96 B/op 1 allocs/op BenchmarkBytesConvStrToBytes-4 1000000000 0.373 ns/op 0 B/op 0 allocs/op Co-authored-by: Bo-Yi Wu --- auth.go | 4 +- binding/form_mapping.go | 5 +- gin.go | 3 +- internal/bytesconv/bytesconv.go | 19 ++++++ internal/bytesconv/bytesconv_test.go | 95 ++++++++++++++++++++++++++++ render/json.go | 14 ++-- 6 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 internal/bytesconv/bytesconv.go create mode 100644 internal/bytesconv/bytesconv_test.go diff --git a/auth.go b/auth.go index c96b1e2..9e5d4cf 100644 --- a/auth.go +++ b/auth.go @@ -8,6 +8,8 @@ import ( "encoding/base64" "net/http" "strconv" + + "github.com/gin-gonic/gin/internal/bytesconv" ) // AuthUserKey is the cookie name for user credential in basic auth. @@ -83,5 +85,5 @@ func processAccounts(accounts Accounts) authPairs { func authorizationHeader(user, password string) string { base := user + ":" + password - return "Basic " + base64.StdEncoding.EncodeToString([]byte(base)) + return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base)) } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index d6199c4..b81ad19 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/internal/json" ) @@ -208,9 +209,9 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel case time.Time: return setTimeField(val, field, value) } - return json.Unmarshal([]byte(val), value.Addr().Interface()) + return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: - return json.Unmarshal([]byte(val), value.Addr().Interface()) + return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) default: return errUnknownType } diff --git a/gin.go b/gin.go index 71f3fd5..0244f18 100644 --- a/gin.go +++ b/gin.go @@ -13,6 +13,7 @@ import ( "path" "sync" + "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/render" ) @@ -477,7 +478,7 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool { rPath := req.URL.Path if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok { - req.URL.Path = string(fixedPath) + req.URL.Path = bytesconv.BytesToString(fixedPath) redirectRequest(c) return true } diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv.go new file mode 100644 index 0000000..32c2b59 --- /dev/null +++ b/internal/bytesconv/bytesconv.go @@ -0,0 +1,19 @@ +package bytesconv + +import ( + "reflect" + "unsafe" +) + +// StringToBytes converts string to byte slice without a memory allocation. +func StringToBytes(s string) (b []byte) { + sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len + return b +} + +// BytesToString converts byte slice to string without a memory allocation. +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go new file mode 100644 index 0000000..ee2c8ab --- /dev/null +++ b/internal/bytesconv/bytesconv_test.go @@ -0,0 +1,95 @@ +package bytesconv + +import ( + "bytes" + "math/rand" + "strings" + "testing" + "time" +) + +var testString = "Albert Einstein: Logic will get you from A to B. Imagination will take you everywhere." +var testBytes = []byte(testString) + +func rawBytesToStr(b []byte) string { + return string(b) +} + +func rawStrToBytes(s string) []byte { + return []byte(s) +} + +// go test -v + +func TestBytesToString(t *testing.T) { + data := make([]byte, 1024) + for i := 0; i < 100; i++ { + rand.Read(data) + if rawBytesToStr(data) != BytesToString(data) { + t.Fatal("don't match") + } + } +} + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + sb.WriteByte(letterBytes[idx]) + i-- + } + cache >>= letterIdxBits + remain-- + } + + return sb.String() +} + +func TestStringToBytes(t *testing.T) { + for i := 0; i < 100; i++ { + s := RandStringBytesMaskImprSrcSB(64) + if !bytes.Equal(rawStrToBytes(s), StringToBytes(s)) { + t.Fatal("don't match") + } + } +} + +// go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true + +func BenchmarkBytesConvBytesToStrRaw(b *testing.B) { + for i := 0; i < b.N; i++ { + rawBytesToStr(testBytes) + } +} + +func BenchmarkBytesConvBytesToStr(b *testing.B) { + for i := 0; i < b.N; i++ { + BytesToString(testBytes) + } +} + +func BenchmarkBytesConvStrToBytesRaw(b *testing.B) { + for i := 0; i < b.N; i++ { + rawStrToBytes(testString) + } +} + +func BenchmarkBytesConvStrToBytes(b *testing.B) { + for i := 0; i < b.N; i++ { + StringToBytes(testString) + } +} diff --git a/render/json.go b/render/json.go index 70506f7..a6fd311 100644 --- a/render/json.go +++ b/render/json.go @@ -10,6 +10,7 @@ import ( "html/template" "net/http" + "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/internal/json" ) @@ -97,8 +98,9 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { return err } // if the jsonBytes is array values - if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) { - _, err = w.Write([]byte(r.Prefix)) + if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes, + bytesconv.StringToBytes("]")) { + _, err = w.Write(bytesconv.StringToBytes(r.Prefix)) if err != nil { return err } @@ -126,11 +128,11 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { } callback := template.JSEscapeString(r.Callback) - _, err = w.Write([]byte(callback)) + _, err = w.Write(bytesconv.StringToBytes(callback)) if err != nil { return err } - _, err = w.Write([]byte("(")) + _, err = w.Write(bytesconv.StringToBytes("(")) if err != nil { return err } @@ -138,7 +140,7 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { if err != nil { return err } - _, err = w.Write([]byte(");")) + _, err = w.Write(bytesconv.StringToBytes(");")) if err != nil { return err } @@ -160,7 +162,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { } var buffer bytes.Buffer - for _, r := range string(ret) { + for _, r := range bytesconv.BytesToString(ret) { cvt := string(r) if r >= 128 { cvt = fmt.Sprintf("\\u%04x", int64(r)) From f94406a087079bfa5a924cc8095ba753160e885c Mon Sep 17 00:00:00 2001 From: ali Date: Mon, 20 Jan 2020 07:12:44 +0000 Subject: [PATCH 441/912] Added support for SameSite cookie flag (#1615) * Added support for SameSite cookie flag * fixed tests. * Update context.go * Update context_test.go * Update context_test.go Co-authored-by: thinkerou Co-authored-by: Bo-Yi Wu --- context.go | 3 ++- context_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index e979d28..ee202d1 100644 --- a/context.go +++ b/context.go @@ -775,7 +775,7 @@ func (c *Context) GetRawData() ([]byte, error) { // SetCookie adds a Set-Cookie header to the ResponseWriter's headers. // The provided cookie must have a valid Name. Invalid cookies may be // silently dropped. -func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { +func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, sameSite http.SameSite, secure, httpOnly bool) { if path == "" { path = "/" } @@ -785,6 +785,7 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, MaxAge: maxAge, Path: path, Domain: domain, + SameSite: sameSite, Secure: secure, HttpOnly: httpOnly, }) diff --git a/context_test.go b/context_test.go index 18709d3..df2d954 100644 --- a/context_test.go +++ b/context_test.go @@ -602,14 +602,14 @@ func TestContextPostFormMultipart(t *testing.T) { func TestContextSetCookie(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.SetCookie("user", "gin", 1, "/", "localhost", true, true) - assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie")) + c.SetCookie("user", "gin", 1, "/", "localhost", http.SameSiteLaxMode, true, true) + assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", c.Writer.Header().Get("Set-Cookie")) } func TestContextSetCookiePathEmpty(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.SetCookie("user", "gin", 1, "", "localhost", true, true) - assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure", c.Writer.Header().Get("Set-Cookie")) + c.SetCookie("user", "gin", 1, "", "localhost", http.SameSiteLaxMode, true, true) + assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", c.Writer.Header().Get("Set-Cookie")) } func TestContextGetCookie(t *testing.T) { From 69a202dbbd51819a2c3fa2aa0c65cb83592961bd Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 22 Jan 2020 00:24:25 +0800 Subject: [PATCH 442/912] chore: upgrade go-isatty and json-iterator/go (#2215) --- go.mod | 4 ++-- go.sum | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 1213bd2..ae98b9e 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.0.1 github.com/golang/protobuf v1.3.2 - github.com/json-iterator/go v1.1.7 - github.com/mattn/go-isatty v0.0.9 + github.com/json-iterator/go v1.1.9 + github.com/mattn/go-isatty v0.0.11 github.com/stretchr/testify v1.4.0 github.com/ugorji/go/codec v1.1.7 gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index 9815f2f..44c34a3 100644 --- a/go.sum +++ b/go.sum @@ -14,12 +14,12 @@ github.com/go-playground/validator/v10 v10.0.1/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= @@ -34,8 +34,8 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 07c0f05f244589ff4a02320a01ad0a1fc102cbd5 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Thu, 23 Jan 2020 07:54:08 +0800 Subject: [PATCH 443/912] Renew README to fit the modification of SetCookie method (#2217) fix #2214 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e0ceb2..966ffbc 100644 --- a/README.md +++ b/README.md @@ -2025,7 +2025,7 @@ func main() { if err != nil { cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", http.SameSiteLaxMode, false, true) } fmt.Printf("Cookie value: %s \n", cookie) From 64e6a7654f134c18e1a21efa9ccd20c477c84f87 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 28 Jan 2020 11:38:45 +0800 Subject: [PATCH 444/912] docs(path): improve comments (#2223) * chore(path): improve comments copy from https://github.com/julienschmidt/httprouter/commit/15782a78c61201cf2fdbc138d63aa60fff114695 * fix typo Signed-off-by: Bo-Yi Wu --- path.go | 24 +++++++++++++++++------- tree.go | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/path.go b/path.go index 37247d4..51346e4 100644 --- a/path.go +++ b/path.go @@ -53,8 +53,9 @@ func cleanPath(p string) string { trailing := n > 1 && p[n-1] == '/' // A bit more clunky without a 'lazybuf' like the path package, but the loop - // gets completely inlined (bufApp). So in contrast to the path package this - // loop has no expensive function calls (except 1x make) + // gets completely inlined (bufApp calls). + // loop has no expensive function calls (except 1x make) // So in contrast to the path package this loop has no expensive function + // calls (except make, if needed). for r < n { switch { @@ -90,14 +91,14 @@ func cleanPath(p string) string { } default: - // real path element. - // add slash if needed + // Real path element. + // Add slash if needed if w > 1 { bufApp(&buf, p, w, '/') w++ } - // copy element + // Copy element for r < n && p[r] != '/' { bufApp(&buf, p, w, p[r]) w++ @@ -106,26 +107,35 @@ func cleanPath(p string) string { } } - // re-append trailing slash + // Re-append trailing slash if trailing && w > 1 { bufApp(&buf, p, w, '/') w++ } + // If the original string was not modified (or only shortened at the end), + // return the respective substring of the original string. + // Otherwise return a new string from the buffer. if len(buf) == 0 { return p[:w] } return string(buf[:w]) } -// internal helper to lazily create a buffer if necessary. +// Internal helper to lazily create a buffer if necessary. +// Calls to this function get inlined. func bufApp(buf *[]byte, s string, w int, c byte) { b := *buf if len(b) == 0 { + // No modification of the original string so far. + // If the next character is the same as in the original string, we do + // not yet have to allocate a buffer. if s[w] == c { return } + // Otherwise use either the stack buffer, if it is large enough, or + // allocate a new buffer on the heap, and copy all previous characters. if l := len(s); l > cap(b) { *buf = make([]byte, len(s)) } else { diff --git a/tree.go b/tree.go index 89f74de..2f6b189 100644 --- a/tree.go +++ b/tree.go @@ -401,7 +401,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle return } - // If no wildcard was found, simple insert the path and handle + // If no wildcard was found, simply insert the path and handle n.path = path n.handlers = handlers n.fullPath = fullPath From 0e4d8eaf07c2d72b548c5157197cbe2115dfb557 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 28 Jan 2020 18:35:47 +0800 Subject: [PATCH 445/912] tree: remove duplicate assignment (#2222) copy from https://github.com/julienschmidt/httprouter/commit/cfa3cb764b4fc4eb98cae67a2020a91c79e065be Co-authored-by: thinkerou --- tree.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/tree.go b/tree.go index 2f6b189..b88eefa 100644 --- a/tree.go +++ b/tree.go @@ -464,7 +464,6 @@ walk: // Outer loop for walking the tree for i, max := 0, len(indices); i < max; i++ { if c == indices[i] { n = n.children[i] - prefix = n.path continue walk } } @@ -508,7 +507,6 @@ walk: // Outer loop for walking the tree if len(n.children) > 0 { path = path[end:] n = n.children[0] - prefix = n.path continue walk } From 731c827892f5b1eac9f58fc65cef32fa1908972c Mon Sep 17 00:00:00 2001 From: Erik Bender Date: Thu, 6 Feb 2020 07:50:21 +0100 Subject: [PATCH 446/912] add yaml negotitation (#2220) Co-authored-by: thinkerou --- context.go | 5 +++++ context_test.go | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index ee202d1..1c1b963 100644 --- a/context.go +++ b/context.go @@ -970,6 +970,7 @@ type Negotiate struct { HTMLData interface{} JSONData interface{} XMLData interface{} + YAMLData interface{} Data interface{} } @@ -988,6 +989,10 @@ func (c *Context) Negotiate(code int, config Negotiate) { data := chooseData(config.XMLData, config.Data) c.XML(code, data) + case binding.MIMEYAML: + data := chooseData(config.YAMLData, config.Data) + c.YAML(code, data) + default: c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck } diff --git a/context_test.go b/context_test.go index df2d954..4380fb5 100644 --- a/context_test.go +++ b/context_test.go @@ -1114,7 +1114,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEJSON, MIMEXML}, + Offered: []string{MIMEJSON, MIMEXML, MIMEYAML}, Data: H{"foo": "bar"}, }) @@ -1129,7 +1129,7 @@ func TestContextNegotiationWithXML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEXML, MIMEJSON}, + Offered: []string{MIMEXML, MIMEJSON, MIMEYAML}, Data: H{"foo": "bar"}, }) From acac7b12102c837752340033624720d245ab2734 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 9 Feb 2020 10:46:22 +0800 Subject: [PATCH 447/912] tree: range over nodes values (#2229) --- tree.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tree.go b/tree.go index b88eefa..b687ec4 100644 --- a/tree.go +++ b/tree.go @@ -169,9 +169,9 @@ walk: } // Update maxParams (max of all children) - for i := range child.children { - if child.children[i].maxParams > child.maxParams { - child.maxParams = child.children[i].maxParams + for _, v := range child.children { + if v.maxParams > child.maxParams { + child.maxParams = v.maxParams } } From 0d12918b0ad1dbf28f61d3d053ae035dbd22a4eb Mon Sep 17 00:00:00 2001 From: thinkerou Date: Thu, 13 Feb 2020 20:23:29 +0800 Subject: [PATCH 448/912] chore: upgrade depend version (#2231) --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index ae98b9e..cfaee74 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module github.com/gin-gonic/gin -go 1.12 +go 1.13 require ( github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.0.1 - github.com/golang/protobuf v1.3.2 + github.com/go-playground/validator/v10 v10.2.0 + github.com/golang/protobuf v1.3.3 github.com/json-iterator/go v1.1.9 - github.com/mattn/go-isatty v0.0.11 + github.com/mattn/go-isatty v0.0.12 github.com/stretchr/testify v1.4.0 github.com/ugorji/go/codec v1.1.7 - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index 44c34a3..d499815 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,4 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= @@ -9,17 +8,17 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.0.1 h1:QgDDZpXlR/L3atIL2PbFt0TpazbtN7N6PxTGcgcyEUg= -github.com/go-playground/validator/v10 v10.0.1/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= @@ -34,11 +33,12 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 863ad2d4deede093860b5234fad9f2a495cc536f Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 21 Feb 2020 16:33:36 +0800 Subject: [PATCH 449/912] docs(badge): add todo badge (#2240) fix https://github.com/gin-gonic/gin/issues/2236 Signed-off-by: Bo-Yi Wu --- README.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 966ffbc..4a0ec90 100644 --- a/README.md +++ b/README.md @@ -10,30 +10,36 @@ [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) +[![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/gin-gonic/gin)](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin) Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. ## Contents -- [Installation](#installation) -- [Quick start](#quick-start) -- [Benchmarks](#benchmarks) -- [Gin v1.stable](#gin-v1-stable) -- [Build with jsoniter](#build-with-jsoniter) -- [API Examples](#api-examples) - - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) +- [Gin Web Framework](#gin-web-framework) + - [Contents](#contents) + - [Installation](#installation) + - [Quick start](#quick-start) + - [Benchmarks](#benchmarks) + - [Gin v1. stable](#gin-v1-stable) + - [Build with jsoniter](#build-with-jsoniter) + - [API Examples](#api-examples) + - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) - [Parameters in path](#parameters-in-path) - [Querystring parameters](#querystring-parameters) - [Multipart/Urlencoded Form](#multiparturlencoded-form) - [Another example: query + post form](#another-example-query--post-form) - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) - [Upload files](#upload-files) + - [Single file](#single-file) + - [Multiple files](#multiple-files) - [Grouping routes](#grouping-routes) - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - [Using middleware](#using-middleware) - [How to write log file](#how-to-write-log-file) - [Custom Log Format](#custom-log-format) + - [Controlling Log output coloring](#controlling-log-output-coloring) - [Model binding and validation](#model-binding-and-validation) - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) @@ -43,10 +49,16 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) - - [JSONP rendering](#jsonp) + - [SecureJSON](#securejson) + - [JSONP](#jsonp) + - [AsciiJSON](#asciijson) + - [PureJSON](#purejson) - [Serving static files](#serving-static-files) - [Serving data from reader](#serving-data-from-reader) - [HTML rendering](#html-rendering) + - [Custom Template renderer](#custom-template-renderer) + - [Custom Delimiters](#custom-delimiters) + - [Custom Template Funcs](#custom-template-funcs) - [Multitemplate](#multitemplate) - [Redirects](#redirects) - [Custom Middleware](#custom-middleware) @@ -62,8 +74,8 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [http2 server push](#http2-server-push) - [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Set and get a cookie](#set-and-get-a-cookie) -- [Testing](#testing) -- [Users](#users) + - [Testing](#testing) + - [Users](#users) ## Installation From 5f56109bcffd58e851dd186282d8fbd4d0decc82 Mon Sep 17 00:00:00 2001 From: Kaushik Neelichetty Date: Fri, 21 Feb 2020 14:45:17 +0530 Subject: [PATCH 450/912] Use json marshall in context json to fix breaking new line issue. Fixes #2209 (#2228) * ignore IntelliJ idea generated files * update JSON renderer to use Marshall() instead of Encode(). Fix #2209 * Revert "ignore IntelliJ idea generated files" This reverts commit e7bd017227df5dbd2ed2f5fe353adb5f1b08c678. Co-authored-by: Bo-Yi Wu Co-authored-by: thinkerou --- context_test.go | 10 +++++----- logger_test.go | 6 +++--- middleware_test.go | 2 +- render/json.go | 7 +++++-- render/render_test.go | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/context_test.go b/context_test.go index 4380fb5..7f0bca3 100644 --- a/context_test.go +++ b/context_test.go @@ -662,7 +662,7 @@ func TestContextRenderJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -690,7 +690,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -716,7 +716,7 @@ func TestContextRenderAPIJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } @@ -1119,7 +1119,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { }) assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1283,7 +1283,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) { _, err := buf.ReadFrom(w.Body) assert.NoError(t, err) jsonStringBody := buf.String() - assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}\n"), jsonStringBody) + assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}"), jsonStringBody) } func TestContextError(t *testing.T) { diff --git a/logger_test.go b/logger_test.go index fc53f35..b587f89 100644 --- a/logger_test.go +++ b/logger_test.go @@ -369,15 +369,15 @@ func TestErrorLogger(t *testing.T) { w := performRequest(router, "GET", "/error") assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"error\":\"this is an error\"}\n", w.Body.String()) + assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) w = performRequest(router, "GET", "/abort") assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Equal(t, "{\"error\":\"no authorized\"}\n", w.Body.String()) + assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) w = performRequest(router, "GET", "/print") assert.Equal(t, http.StatusInternalServerError, w.Code) - assert.Equal(t, "hola!{\"error\":\"this is an error\"}\n", w.Body.String()) + assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } func TestLoggerWithWriterSkippingPaths(t *testing.T) { diff --git a/middleware_test.go b/middleware_test.go index 2ae9e88..fca1c53 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -246,5 +246,5 @@ func TestMiddlewareWrite(t *testing.T) { w := performRequest(router, "GET", "/") assert.Equal(t, http.StatusBadRequest, w.Code) - assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}\n{\"foo\":\"bar\"}\nevent:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) + assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) } diff --git a/render/json.go b/render/json.go index a6fd311..015c0db 100644 --- a/render/json.go +++ b/render/json.go @@ -69,8 +69,11 @@ func (r JSON) WriteContentType(w http.ResponseWriter) { // WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) - encoder := json.NewEncoder(w) - err := encoder.Encode(&obj) + jsonBytes, err := json.Marshal(obj) + if err != nil { + return err + } + _, err = w.Write(jsonBytes) return err } diff --git a/render/render_test.go b/render/render_test.go index d0b5615..353c82b 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -36,7 +36,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } From 094b3fdb393ff8eab16ec293bf49513213955523 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 26 Feb 2020 10:27:03 +0800 Subject: [PATCH 451/912] ci support go1.14 (#2262) --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 582b732..6680a5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,10 @@ matrix: - go: 1.13.x env: - TESTTAGS=nomsgpack + - go: 1.14.x + - go: 1.14.x + env: + - TESTTAGS=nomsgpack - go: master git: From 2ff2b19e14420de970264012e1bcdf91929725b4 Mon Sep 17 00:00:00 2001 From: kebo Date: Sat, 7 Mar 2020 09:21:02 +0800 Subject: [PATCH 452/912] fix accept incoming network connections (#2216) --- utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils.go b/utils.go index 71b80de..77b5f0c 100644 --- a/utils.go +++ b/utils.go @@ -139,10 +139,10 @@ func resolveAddress(addr []string) string { case 0: if port := os.Getenv("PORT"); port != "" { debugPrint("Environment variable PORT=\"%s\"", port) - return ":" + port + return "localhost:" + port } debugPrint("Environment variable PORT is undefined. Using port :8080 by default") - return ":8080" + return "localhost:8080" case 1: return addr[0] default: From 1d055af1bc15ab3c5965ce0bf99c6f3f44465b56 Mon Sep 17 00:00:00 2001 From: Nikifor Seryakov Date: Sat, 7 Mar 2020 05:23:33 +0300 Subject: [PATCH 453/912] FileFromFS (#2112) * Context.FileFromFS added * Context File and FileFromFS examples at README Co-authored-by: Bo-Yi Wu --- README.md | 18 ++++++++++++++++++ context.go | 11 +++++++++++ context_test.go | 13 +++++++++++++ 3 files changed, 42 insertions(+) diff --git a/README.md b/README.md index 4a0ec90..709a15b 100644 --- a/README.md +++ b/README.md @@ -1182,6 +1182,24 @@ func main() { } ``` +### Serving data from file + +```go +func main() { + router := gin.Default() + + router.GET("/local/file", func(c *gin.Context) { + c.File("local/file.go") + }) + + var fs http.FileSystem = // ... + router.GET("/fs/file", func(c *gin.Context) { + c.FileFromFS("fs/file.go", fs) + }) +} + +``` + ### Serving data from reader ```go diff --git a/context.go b/context.go index 1c1b963..1d3e665 100644 --- a/context.go +++ b/context.go @@ -925,6 +925,17 @@ func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) } +// FileFromFS writes the specified file from http.FileSytem into the body stream in an efficient way. +func (c *Context) FileFromFS(filepath string, fs http.FileSystem) { + defer func(old string) { + c.Request.URL.Path = old + }(c.Request.URL.Path) + + c.Request.URL.Path = filepath + + http.FileServer(fs).ServeHTTP(c.Writer, c.Request) +} + // FileAttachment writes the specified file into the body stream in an efficient way // On the client side, the file will typically be downloaded with the given filename func (c *Context) FileAttachment(filepath, filename string) { diff --git a/context_test.go b/context_test.go index 7f0bca3..78b22c0 100644 --- a/context_test.go +++ b/context_test.go @@ -992,6 +992,19 @@ func TestContextRenderFile(t *testing.T) { assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } +func TestContextRenderFileFromFS(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("GET", "/some/path", nil) + c.FileFromFS("./gin.go", Dir(".", false)) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "/some/path", c.Request.URL.Path) +} + func TestContextRenderAttachment(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From a71af9c144f9579f6dbe945341c1df37aaf09c0d Mon Sep 17 00:00:00 2001 From: Manuel Alonso Date: Sat, 7 Mar 2020 14:51:33 +0100 Subject: [PATCH 454/912] removing log injection (#2277) Co-authored-by: thinkerou --- logger.go | 2 +- logger_test.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/logger.go b/logger.go index d5b96b3..d361b74 100644 --- a/logger.go +++ b/logger.go @@ -141,7 +141,7 @@ var defaultLogFormatter = func(param LogFormatterParams) string { // Truncate in a golang < 1.8 safe way param.Latency = param.Latency - param.Latency%time.Second } - return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", + return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), statusColor, param.StatusCode, resetColor, param.Latency, diff --git a/logger_test.go b/logger_test.go index b587f89..0d40666 100644 --- a/logger_test.go +++ b/logger_test.go @@ -158,7 +158,7 @@ func TestLoggerWithFormatter(t *testing.T) { router := New() router.Use(LoggerWithFormatter(func(param LogFormatterParams) string { - return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s", + return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %#v\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), param.StatusCode, param.Latency, @@ -275,11 +275,11 @@ func TestDefaultLogFormatter(t *testing.T) { isTerm: false, } - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseLongDurationParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET \"/\"\n", defaultLogFormatter(termFalseLongDurationParam)) - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam)) - assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueLongDurationParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam)) } From 67008be35f5432aeec06641540f3b6df3eac8747 Mon Sep 17 00:00:00 2001 From: "Ryan J. Yoder" Date: Mon, 16 Mar 2020 07:36:15 -0700 Subject: [PATCH 455/912] Unix Socket Handling (#2280) * do not set unix socket permissions. Cleanup unix socket. * removed useless error checking --- gin.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gin.go b/gin.go index 0244f18..ab1d0a4 100644 --- a/gin.go +++ b/gin.go @@ -320,16 +320,13 @@ func (engine *Engine) RunUnix(file string) (err error) { debugPrint("Listening and serving HTTP on unix:/%s", file) defer func() { debugPrintError(err) }() - os.Remove(file) listener, err := net.Listen("unix", file) if err != nil { return } defer listener.Close() - err = os.Chmod(file, 0777) - if err != nil { - return - } + defer os.Remove(file) + err = http.Serve(listener, engine) return } From 73ccfea3ba5a115e74177dbfbc1ea0fff88c13f4 Mon Sep 17 00:00:00 2001 From: AcoNCodes Date: Mon, 16 Mar 2020 18:52:02 +0200 Subject: [PATCH 456/912] Add mutex for protect Context.Keys map (#1391) * Add mutex for protect Context.Keys map * Fix tests Co-authored-by: Nikolay Tolkachov Co-authored-by: Bo-Yi Wu --- context.go | 10 ++++++++++ gin.go | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 1d3e665..572dbb8 100644 --- a/context.go +++ b/context.go @@ -16,6 +16,7 @@ import ( "net/url" "os" "strings" + "sync" "time" "github.com/gin-contrib/sse" @@ -52,6 +53,9 @@ type Context struct { engine *Engine + // This mutex protect Keys map + KeysMutex *sync.RWMutex + // Keys is a key/value pair exclusively for the context of each request. Keys map[string]interface{} @@ -78,6 +82,7 @@ func (c *Context) reset() { c.Params = c.Params[0:0] c.handlers = nil c.index = -1 + c.KeysMutex = &sync.RWMutex{} c.fullPath = "" c.Keys = nil c.Errors = c.Errors[0:0] @@ -219,16 +224,21 @@ func (c *Context) Error(err error) *Error { // Set is used to store a new key/value pair exclusively for this context. // It also lazy initializes c.Keys if it was not used previously. func (c *Context) Set(key string, value interface{}) { + c.KeysMutex.Lock() if c.Keys == nil { c.Keys = make(map[string]interface{}) } + c.Keys[key] = value + c.KeysMutex.Unlock() } // Get returns the value for the given key, ie: (value, true). // If the value does not exists it returns (nil, false) func (c *Context) Get(key string) (value interface{}, exists bool) { + c.KeysMutex.RLock() value, exists = c.Keys[key] + c.KeysMutex.RUnlock() return } diff --git a/gin.go b/gin.go index ab1d0a4..1c2acbc 100644 --- a/gin.go +++ b/gin.go @@ -162,7 +162,7 @@ func Default() *Engine { } func (engine *Engine) allocateContext() *Context { - return &Context{engine: engine} + return &Context{engine: engine, KeysMutex: &sync.RWMutex{}} } // Delims sets template left and right delims and returns a Engine instance. From c4fd2489ced13e86c6e9328e7d66cd3bb2957f00 Mon Sep 17 00:00:00 2001 From: "Igor H. Vieira" Date: Sat, 21 Mar 2020 23:25:35 -0300 Subject: [PATCH 457/912] =?UTF-8?q?Improved=20the=20graceful=20shutdown=20?= =?UTF-8?q?and=20restart=20section=20and=20removed=E2=80=A6=20(#2288)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 709a15b..998783a 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [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) + - [Graceful shutdown or restart](#graceful-shutdown-or-restart) - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) @@ -1687,12 +1687,13 @@ func main() { } ``` -### Graceful restart or stop +### Graceful shutdown or restart -Do you want to graceful restart or stop your web server? -There are some ways this can be done. +There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages. -We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. +#### Third-party packages + +We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. ```go router := gin.Default() @@ -1701,13 +1702,15 @@ router.GET("/", handler) endless.ListenAndServe(":4242", router) ``` -An alternative to endless: +Alternatives: * [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. * [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. * [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. -If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown) example with gin. +#### Manually + +In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). ```go // +build go1.8 @@ -1738,8 +1741,9 @@ func main() { Handler: router, } + // Initializing the server in a goroutine so that + // it won't block the graceful shutdown handling below go func() { - // service connections if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } @@ -1753,18 +1757,16 @@ func main() { // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit - log.Println("Shutdown Server ...") + log.Println("Shuting down server...") + // The context is used to inform the server it has 5 seconds to finish + // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server Shutdown:", err) - } - // catching ctx.Done(). timeout of 5 seconds. - select { - case <-ctx.Done(): - log.Println("timeout of 5 seconds.") + log.Fatal("Server forced to shutdown:", err) } + log.Println("Server exiting") } ``` From a412209e60c4b9562bd723e9e01019829e357a39 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 22 Mar 2020 12:28:46 +0800 Subject: [PATCH 458/912] docs: Add 1.6 changelogs (#2290) --- CHANGELOG.md | 76 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ceb919..0fb00fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,54 @@ -### Gin v1.5.0 +## Gin v1.6.0 (Mar 22, 2020) + +### BREAKING + * chore(performance): Improve performance for adding RemoveExtraSlash flag (#2159) + * drop support govendor (#2148) + * Added support for SameSite cookie flag (#1615) +### FEATURES + * add yaml negotitation (#2220) + * FileFromFS (#2112) +### BUGFIXES + * Unix Socket Handling (#2280) + * Use json marshall in context json to fix breaking new line issue. Fixes #2209 (#2228) + * fix accept incoming network connections (#2216) + * Fixed a bug in the calculation of the maximum number of parameters (#2166) + * [FIX] allow empty headers on DataFromReader (#2121) + * Add mutex for protect Context.Keys map (#1391) +### ENHANCEMENTS + * Add mitigation for log injection (#2277) + * tree: range over nodes values (#2229) + * tree: remove duplicate assignment (#2222) + * chore: upgrade go-isatty and json-iterator/go (#2215) + * path: sync code with httprouter (#2212) + * Use zero-copy approach to convert types between string and byte slice (#2206) + * Reuse bytes when cleaning the URL paths (#2179) + * tree: remove one else statement (#2177) + * tree: sync httprouter update (#2173) (#2172) (#2171) + * tree: sync part httprouter codes and reduce if/else (#2163) + * use http method constant (#2155) + * upgrade go-validator to v10 (#2149) + * Refactor redirect request in gin.go (#1970) + * Add build tag nomsgpack (#1852) +### DOCS + * docs(path): improve comments (#2223) + * Renew README to fit the modification of SetCookie method (#2217) + * Fix spelling (#2202) + * Remove broken link from README. (#2198) + * Update docs on Context.Done(), Context.Deadline() and Context.Err() (#2196) + * Update validator to v10 (#2190) + * upgrade go-validator to v10 for README (#2189) + * Update to currently output (#2188) + * Fix "Custom Validators" example (#2186) + * Add project to README (#2165) + * docs(benchmarks): for gin v1.5 (#2153) + * Changed wording for clarity in README.md (#2122) +### MISC + * ci support go1.14 (#2262) + * chore: upgrade depend version (#2231) + * Drop support go1.10 (#2147) + * fix comment in `mode.go` (#2129) + +## Gin v1.5.0 - [FIX] Use DefaultWriter and DefaultErrorWriter for debug messages [#1891](https://github.com/gin-gonic/gin/pull/1891) - [NEW] Now you can parse the inline lowercase start structure [#1893](https://github.com/gin-gonic/gin/pull/1893) @@ -90,7 +140,7 @@ - [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491) -### Gin v1.3.0 +## Gin v1.3.0 - [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383) - [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358) @@ -112,7 +162,7 @@ - [FIX] Gin Mode `""` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250) - [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460) -### Gin 1.2.0 +## Gin 1.2.0 - [NEW] Switch from godeps to govendor - [NEW] Add support for Let's Encrypt via gin-gonic/autotls @@ -135,15 +185,15 @@ - [FIX] Use X-Forwarded-For before X-Real-Ip - [FIX] time.Time binding (#904) -### Gin 1.1.4 +## Gin 1.1.4 - [NEW] Support google appengine for IsTerminal func -### Gin 1.1.3 +## Gin 1.1.3 - [FIX] Reverted Logger: skip ANSI color commands -### Gin 1.1 +## Gin 1.1 - [NEW] Implement QueryArray and PostArray methods - [NEW] Refactor GetQuery and GetPostForm @@ -153,7 +203,7 @@ - [FIX] Changed imports to gopkg instead of github in README (#733) - [FIX] Logger: skip ANSI color commands if output is not a tty -### Gin 1.0rc2 (...) +## Gin 1.0rc2 (...) - [PERFORMANCE] Fast path for writing Content-Type. - [PERFORMANCE] Much faster 404 routing @@ -188,7 +238,7 @@ - [FIX] MIT license in every file -### Gin 1.0rc1 (May 22, 2015) +## Gin 1.0rc1 (May 22, 2015) - [PERFORMANCE] Zero allocation router - [PERFORMANCE] Faster JSON, XML and text rendering @@ -232,7 +282,7 @@ - [FIX] Better support for Google App Engine (using log instead of fmt) -### Gin 0.6 (Mar 9, 2015) +## Gin 0.6 (Mar 9, 2015) - [NEW] Support multipart/form-data - [NEW] NoMethod handler @@ -242,14 +292,14 @@ - [FIX] Improve color logger -### Gin 0.5 (Feb 7, 2015) +## Gin 0.5 (Feb 7, 2015) - [NEW] Content Negotiation - [FIX] Solved security bug that allow a client to spoof ip - [FIX] Fix unexported/ignored fields in binding -### Gin 0.4 (Aug 21, 2014) +## Gin 0.4 (Aug 21, 2014) - [NEW] Development mode - [NEW] Unit tests @@ -258,7 +308,7 @@ - [FIX] Improved documentation for model binding -### Gin 0.3 (Jul 18, 2014) +## Gin 0.3 (Jul 18, 2014) - [PERFORMANCE] Normal log and error log are printed in the same call. - [PERFORMANCE] Improve performance of NoRouter() @@ -276,7 +326,7 @@ - [FIX] Check application/x-www-form-urlencoded when parsing form -### Gin 0.2b (Jul 08, 2014) +## Gin 0.2b (Jul 08, 2014) - [PERFORMANCE] Using sync.Pool to allocatio/gc overhead - [NEW] Travis CI integration - [NEW] Completely new logger From ae888314485066c70f31d6d06f3ff6afa635cb12 Mon Sep 17 00:00:00 2001 From: Henry Kwan Date: Mon, 23 Mar 2020 13:52:28 +0800 Subject: [PATCH 459/912] Update version.go (#2293) to sync with tag v1.6.0 --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 6f8235f..b024c50 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.5.0" +const Version = "v1.6.0" From 1bebd9af9119315dab3870a1011369a11c886a2a Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 23 Mar 2020 17:48:25 +0800 Subject: [PATCH 460/912] Revert "fix accept incoming network connections (#2216)" (#2294) This reverts commit 2ff2b19e14420de970264012e1bcdf91929725b4. --- utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils.go b/utils.go index 77b5f0c..71b80de 100644 --- a/utils.go +++ b/utils.go @@ -139,10 +139,10 @@ func resolveAddress(addr []string) string { case 0: if port := os.Getenv("PORT"); port != "" { debugPrint("Environment variable PORT=\"%s\"", port) - return "localhost:" + port + return ":" + port } debugPrint("Environment variable PORT is undefined. Using port :8080 by default") - return "localhost:8080" + return ":8080" case 1: return addr[0] default: From 07a6818d24f9b0e3c97b6c44e19af877003bad46 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 23 Mar 2020 18:00:58 +0800 Subject: [PATCH 461/912] bump to v1.6.1 version (#2295) Co-authored-by: thinkerou --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index b024c50..ce6203c 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.6.0" +const Version = "v1.6.1" From bd5ee1aae2e34e45c7401c3b11c70ac8feac7b41 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Tue, 24 Mar 2020 22:49:34 +0800 Subject: [PATCH 462/912] doc: add pr link (#2298) * doc: add pr link * doc: add v1.6.1 release note --- CHANGELOG.md | 89 +++++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb00fa..02459d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,52 +1,57 @@ -## Gin v1.6.0 (Mar 22, 2020) +## Gin v1.6.1 + +### BUFIXES + * Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294) + +## Gin v1.6.0 ### BREAKING - * chore(performance): Improve performance for adding RemoveExtraSlash flag (#2159) - * drop support govendor (#2148) - * Added support for SameSite cookie flag (#1615) + * chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159) + * drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148) + * Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615) ### FEATURES - * add yaml negotitation (#2220) - * FileFromFS (#2112) + * add yaml negotitation [#2220](https://github.com/gin-gonic/gin/pull/2220) + * FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112) ### BUGFIXES - * Unix Socket Handling (#2280) - * Use json marshall in context json to fix breaking new line issue. Fixes #2209 (#2228) - * fix accept incoming network connections (#2216) - * Fixed a bug in the calculation of the maximum number of parameters (#2166) - * [FIX] allow empty headers on DataFromReader (#2121) - * Add mutex for protect Context.Keys map (#1391) + * Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280) + * Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228) + * fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216) + * Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166) + * [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121) + * Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391) ### ENHANCEMENTS - * Add mitigation for log injection (#2277) - * tree: range over nodes values (#2229) - * tree: remove duplicate assignment (#2222) - * chore: upgrade go-isatty and json-iterator/go (#2215) - * path: sync code with httprouter (#2212) - * Use zero-copy approach to convert types between string and byte slice (#2206) - * Reuse bytes when cleaning the URL paths (#2179) - * tree: remove one else statement (#2177) - * tree: sync httprouter update (#2173) (#2172) (#2171) - * tree: sync part httprouter codes and reduce if/else (#2163) - * use http method constant (#2155) - * upgrade go-validator to v10 (#2149) - * Refactor redirect request in gin.go (#1970) - * Add build tag nomsgpack (#1852) + * Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277) + * tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229) + * tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222) + * chore: upgrade go-isatty and json-iterator/go [#2215](https://github.com/gin-gonic/gin/pull/2215) + * path: sync code with httprouter [#2212](https://github.com/gin-gonic/gin/pull/2212) + * Use zero-copy approach to convert types between string and byte slice [#2206](https://github.com/gin-gonic/gin/pull/2206) + * Reuse bytes when cleaning the URL paths [#2179](https://github.com/gin-gonic/gin/pull/2179) + * tree: remove one else statement [#2177](https://github.com/gin-gonic/gin/pull/2177) + * tree: sync httprouter update (#2173) (#2172) [#2171](https://github.com/gin-gonic/gin/pull/2171) + * tree: sync part httprouter codes and reduce if/else [#2163](https://github.com/gin-gonic/gin/pull/2163) + * use http method constant [#2155](https://github.com/gin-gonic/gin/pull/2155) + * upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149) + * Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970) + * Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852) ### DOCS - * docs(path): improve comments (#2223) - * Renew README to fit the modification of SetCookie method (#2217) - * Fix spelling (#2202) - * Remove broken link from README. (#2198) - * Update docs on Context.Done(), Context.Deadline() and Context.Err() (#2196) - * Update validator to v10 (#2190) - * upgrade go-validator to v10 for README (#2189) - * Update to currently output (#2188) - * Fix "Custom Validators" example (#2186) - * Add project to README (#2165) - * docs(benchmarks): for gin v1.5 (#2153) - * Changed wording for clarity in README.md (#2122) + * docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223) + * Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217) + * Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202) + * Remove broken link from README. [#2198](https://github.com/gin-gonic/gin/pull/2198) + * Update docs on Context.Done(), Context.Deadline() and Context.Err() [#2196](https://github.com/gin-gonic/gin/pull/2196) + * Update validator to v10 [#2190](https://github.com/gin-gonic/gin/pull/2190) + * upgrade go-validator to v10 for README [#2189](https://github.com/gin-gonic/gin/pull/2189) + * Update to currently output [#2188](https://github.com/gin-gonic/gin/pull/2188) + * Fix "Custom Validators" example [#2186](https://github.com/gin-gonic/gin/pull/2186) + * Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165) + * docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153) + * Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122) ### MISC - * ci support go1.14 (#2262) - * chore: upgrade depend version (#2231) - * Drop support go1.10 (#2147) - * fix comment in `mode.go` (#2129) + * ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262) + * chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231) + * Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147) + * fix comment in `mode.go` [#2129](https://github.com/gin-gonic/gin/pull/2129) ## Gin v1.5.0 From 57f99ca50fd368c9fdc4543e22d6e84b88571ae5 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 27 Mar 2020 10:47:22 +0800 Subject: [PATCH 463/912] Add set samesite in cookie. (#2306) Signed-off-by: Bo-Yi Wu --- context.go | 13 +++++++++++-- context_test.go | 6 ++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index 572dbb8..5c1fab2 100644 --- a/context.go +++ b/context.go @@ -71,6 +71,10 @@ type Context struct { // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH, // or PUT body parameters. formCache url.Values + + // SameSite allows a server to define a cookie attribute making it impossible for + // the browser to send this cookie along with cross-site requests. + sameSite http.SameSite } /************************************/ @@ -782,10 +786,15 @@ func (c *Context) GetRawData() ([]byte, error) { return ioutil.ReadAll(c.Request.Body) } +// SetSameSite with cookie +func (c *Context) SetSameSite(samesite http.SameSite) { + c.sameSite = samesite +} + // SetCookie adds a Set-Cookie header to the ResponseWriter's headers. // The provided cookie must have a valid Name. Invalid cookies may be // silently dropped. -func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, sameSite http.SameSite, secure, httpOnly bool) { +func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { if path == "" { path = "/" } @@ -795,7 +804,7 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, MaxAge: maxAge, Path: path, Domain: domain, - SameSite: sameSite, + SameSite: c.sameSite, Secure: secure, HttpOnly: httpOnly, }) diff --git a/context_test.go b/context_test.go index 78b22c0..80c7b40 100644 --- a/context_test.go +++ b/context_test.go @@ -602,13 +602,15 @@ func TestContextPostFormMultipart(t *testing.T) { func TestContextSetCookie(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.SetCookie("user", "gin", 1, "/", "localhost", http.SameSiteLaxMode, true, true) + c.SetSameSite(http.SameSiteLaxMode) + c.SetCookie("user", "gin", 1, "/", "localhost", true, true) assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", c.Writer.Header().Get("Set-Cookie")) } func TestContextSetCookiePathEmpty(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.SetCookie("user", "gin", 1, "", "localhost", http.SameSiteLaxMode, true, true) + c.SetSameSite(http.SameSiteLaxMode) + c.SetCookie("user", "gin", 1, "", "localhost", true, true) assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", c.Writer.Header().Get("Set-Cookie")) } From 298ebca69107001096eaf81c4b4977b14ffa0e6d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 27 Mar 2020 10:57:36 +0800 Subject: [PATCH 464/912] fix missing initial sync.RWMutex (#2305) * fix missing initial sync.RWMutex Signed-off-by: Bo-Yi Wu * Add unit testing. Signed-off-by: Bo-Yi Wu --- context.go | 8 ++++++++ context_test.go | 13 +++++++++++++ go.sum | 1 + 3 files changed, 22 insertions(+) diff --git a/context.go b/context.go index 5c1fab2..a2384c0 100644 --- a/context.go +++ b/context.go @@ -228,6 +228,10 @@ func (c *Context) Error(err error) *Error { // Set is used to store a new key/value pair exclusively for this context. // It also lazy initializes c.Keys if it was not used previously. func (c *Context) Set(key string, value interface{}) { + if c.KeysMutex == nil { + c.KeysMutex = &sync.RWMutex{} + } + c.KeysMutex.Lock() if c.Keys == nil { c.Keys = make(map[string]interface{}) @@ -240,6 +244,10 @@ func (c *Context) Set(key string, value interface{}) { // Get returns the value for the given key, ie: (value, true). // If the value does not exists it returns (nil, false) func (c *Context) Get(key string) (value interface{}, exists bool) { + if c.KeysMutex == nil { + c.KeysMutex = &sync.RWMutex{} + } + c.KeysMutex.RLock() value, exists = c.Keys[key] c.KeysMutex.RUnlock() diff --git a/context_test.go b/context_test.go index 80c7b40..ce077bc 100644 --- a/context_test.go +++ b/context_test.go @@ -1920,3 +1920,16 @@ func TestRaceParamsContextCopy(t *testing.T) { performRequest(router, "GET", "/name2/api") wg.Wait() } + +func TestContextWithKeysMutex(t *testing.T) { + c := &Context{} + c.Set("foo", "bar") + + value, err := c.Get("foo") + assert.Equal(t, "bar", value) + assert.True(t, err) + + value, err = c.Get("foo2") + assert.Nil(t, value) + assert.False(t, err) +} diff --git a/go.sum b/go.sum index d499815..4c14fb8 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= From 3315353c20ab224d9308db9df19d83383dba539a Mon Sep 17 00:00:00 2001 From: Henry Kwan Date: Fri, 27 Mar 2020 21:39:11 +0800 Subject: [PATCH 465/912] Update version.go (#2307) sync to tag v1.6.2 --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index ce6203c..883bdad 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.6.1" +const Version = "v1.6.2" From 4f208887e1231459672a2a9fc1b2aa40486825d4 Mon Sep 17 00:00:00 2001 From: Shilin Wang Date: Wed, 8 Apr 2020 23:31:31 +0800 Subject: [PATCH 466/912] update set cookie example (#2312) fix #2308 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 998783a..9dde693 100644 --- a/README.md +++ b/README.md @@ -2057,7 +2057,7 @@ func main() { if err != nil { cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", http.SameSiteLaxMode, false, true) + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) } fmt.Printf("Cookie value: %s \n", cookie) From a4e947a356add617b5022c31f6dc28e5b78849fe Mon Sep 17 00:00:00 2001 From: bestgopher <84328409@qq.com> Date: Thu, 16 Apr 2020 22:31:58 +0800 Subject: [PATCH 467/912] update:SetMode function (#2321) --- mode.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mode.go b/mode.go index edfc294..ca3677a 100644 --- a/mode.go +++ b/mode.go @@ -50,8 +50,12 @@ func init() { // SetMode sets gin mode according to input string. func SetMode(value string) { + if value == "" { + value = DebugMode + } + switch value { - case DebugMode, "": + case DebugMode: ginMode = debugCode case ReleaseMode: ginMode = releaseCode @@ -60,9 +64,7 @@ func SetMode(value string) { default: panic("gin mode unknown: " + value) } - if value == "" { - value = DebugMode - } + modeName = value } From 90fff292d7befc9794679cc13cb179ee94f603b8 Mon Sep 17 00:00:00 2001 From: Johnny Dallas Date: Thu, 16 Apr 2020 21:26:42 -0700 Subject: [PATCH 468/912] fix typo in the PR template and CONTRIBUTING files (#2323) Co-authored-by: Bo-Yi Wu --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8630bc3..e86bc98 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,7 @@ - With pull requests: - Open your pull request against `master` - Your pull request should have no more than two commits, if not you should squash them. - - It should pass all tests in the available continuous integrations systems such as TravisCI. + - It should pass all tests in the available continuous integration systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 547b777..98d758e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,6 @@ - With pull requests: - Open your pull request against `master` - Your pull request should have no more than two commits, if not you should squash them. - - It should pass all tests in the available continuous integrations systems such as TravisCI. + - It should pass all tests in the available continuous integration systems such as TravisCI. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README. From be4ba7d9df52a96c34715a89a75197aeff2f921a Mon Sep 17 00:00:00 2001 From: Qt Date: Mon, 20 Apr 2020 20:07:36 +0800 Subject: [PATCH 469/912] update: CHANGELOG.md add log for version 1.6.2 (#2329) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02459d5..c26b765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## Gin v1.6.2 + +### BUFIXES + * fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305) +### ENHANCEMENTS + * Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306) + ## Gin v1.6.1 ### BUFIXES From 4427ca4a607802d2cd45268d83a180ef6aa0b5d4 Mon Sep 17 00:00:00 2001 From: Ronald Petty Date: Mon, 27 Apr 2020 18:36:04 -0700 Subject: [PATCH 470/912] Update gin_integration_test.go (#2341) * Update gin_integration_test.go TestUnixSocket fails if you run it twice in a row. This is due to the unix socket file persisting. Added defer to clean up. Whats unclear is the following test TestBadUnixSocket I suspect is just looking for cruft maybe from a prior test or defaults not working, I have not enough background to say. * Update gin_integration_test.go I believe there is some tab issue here, tried to manual overwrite it now. * Update gin_integration_test.go squash you dang spaces!!! --- gin_integration_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index f29d1fc..5f508c7 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -146,15 +146,19 @@ func TestRunWithPort(t *testing.T) { func TestUnixSocket(t *testing.T) { router := New() + unixTestSocket := "/tmp/unix_unit_test" + + defer os.Remove(unixTestSocket) + go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - assert.NoError(t, router.RunUnix("/tmp/unix_unit_test")) + assert.NoError(t, router.RunUnix(unixTestSocket)) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - c, err := net.Dial("unix", "/tmp/unix_unit_test") + c, err := net.Dial("unix", unixTestSocket) assert.NoError(t, err) fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n") From 2c43278080f90f1b3d3fd52784d765290d36d253 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 3 May 2020 20:39:34 +0800 Subject: [PATCH 471/912] chore(performance): Change *sync.RWMutex to sync.RWMutex (#2351) --- context.go | 27 ++++++++++++--------------- gin.go | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/context.go b/context.go index a2384c0..fb7f54e 100644 --- a/context.go +++ b/context.go @@ -54,7 +54,7 @@ type Context struct { engine *Engine // This mutex protect Keys map - KeysMutex *sync.RWMutex + mu sync.RWMutex // Keys is a key/value pair exclusively for the context of each request. Keys map[string]interface{} @@ -86,7 +86,7 @@ func (c *Context) reset() { c.Params = c.Params[0:0] c.handlers = nil c.index = -1 - c.KeysMutex = &sync.RWMutex{} + c.fullPath = "" c.Keys = nil c.Errors = c.Errors[0:0] @@ -98,7 +98,12 @@ func (c *Context) reset() { // Copy returns a copy of the current context that can be safely used outside the request's scope. // This has to be used when the context has to be passed to a goroutine. func (c *Context) Copy() *Context { - var cp = *c + cp := Context{ + writermem: c.writermem, + Request: c.Request, + Params: c.Params, + engine: c.engine, + } cp.writermem.ResponseWriter = nil cp.Writer = &cp.writermem cp.index = abortIndex @@ -228,29 +233,21 @@ func (c *Context) Error(err error) *Error { // Set is used to store a new key/value pair exclusively for this context. // It also lazy initializes c.Keys if it was not used previously. func (c *Context) Set(key string, value interface{}) { - if c.KeysMutex == nil { - c.KeysMutex = &sync.RWMutex{} - } - - c.KeysMutex.Lock() + c.mu.Lock() if c.Keys == nil { c.Keys = make(map[string]interface{}) } c.Keys[key] = value - c.KeysMutex.Unlock() + c.mu.Unlock() } // Get returns the value for the given key, ie: (value, true). // If the value does not exists it returns (nil, false) func (c *Context) Get(key string) (value interface{}, exists bool) { - if c.KeysMutex == nil { - c.KeysMutex = &sync.RWMutex{} - } - - c.KeysMutex.RLock() + c.mu.RLock() value, exists = c.Keys[key] - c.KeysMutex.RUnlock() + c.mu.RUnlock() return } diff --git a/gin.go b/gin.go index 1c2acbc..ab1d0a4 100644 --- a/gin.go +++ b/gin.go @@ -162,7 +162,7 @@ func Default() *Engine { } func (engine *Engine) allocateContext() *Context { - return &Context{engine: engine, KeysMutex: &sync.RWMutex{}} + return &Context{engine: engine} } // Delims sets template left and right delims and returns a Engine instance. From abc4fa07189cb1f2ed23ec0d3aeac0d34d6348ff Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 3 May 2020 21:12:07 +0800 Subject: [PATCH 472/912] Release v1.6.3 version (#2353) * chore: update version to v1.6.3 * update changelog Signed-off-by: Bo-Yi Wu --- CHANGELOG.md | 8 ++++++++ version.go | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c26b765..592c2ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# Gin ChangeLog + +## Gin v1.6.3 + +### ENHANCEMENTS + + * Improve performance: Change `*sync.RWMutex` to `sync.RWMutex` in context. [#2351](https://github.com/gin-gonic/gin/pull/2351) + ## Gin v1.6.2 ### BUFIXES diff --git a/version.go b/version.go index 883bdad..3e9687d 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.6.2" +const Version = "v1.6.3" From 54175dbe72289b7524b53a0afd2e5bddfa7b6efc Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 4 May 2020 11:40:41 +0800 Subject: [PATCH 473/912] chore: update the result of CR (#2354) * chore: update the result of CR * Update utils.go * Update utils.go * Update context.go * Update context.go --- auth.go | 5 +++-- context.go | 4 +++- gin.go | 7 ++++--- mode.go | 1 + path.go | 7 ++++--- recovery.go | 2 +- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/auth.go b/auth.go index 9e5d4cf..43ad36f 100644 --- a/auth.go +++ b/auth.go @@ -70,8 +70,9 @@ func BasicAuth(accounts Accounts) HandlerFunc { } func processAccounts(accounts Accounts) authPairs { - assert1(len(accounts) > 0, "Empty list of authorized credentials") - pairs := make(authPairs, 0, len(accounts)) + length := len(accounts) + assert1(length > 0, "Empty list of authorized credentials") + pairs := make(authPairs, 0, length) for user, password := range accounts { assert1(user != "", "User can not be empty") value := authorizationHeader(user, password) diff --git a/context.go b/context.go index fb7f54e..01cff1a 100644 --- a/context.go +++ b/context.go @@ -34,9 +34,11 @@ const ( MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm MIMEYAML = binding.MIMEYAML - BodyBytesKey = "_gin-gonic/gin/bodybyteskey" ) +// BodyBytesKey indicates a default body bytes key. +const BodyBytesKey = "_gin-gonic/gin/bodybyteskey" + const abortIndex int8 = math.MaxInt8 / 2 // Context is the most important part of gin. It allows us to pass variables between middleware, diff --git a/gin.go b/gin.go index ab1d0a4..888be36 100644 --- a/gin.go +++ b/gin.go @@ -20,11 +20,12 @@ import ( const defaultMultipartMemory = 32 << 20 // 32 MB var ( - default404Body = []byte("404 page not found") - default405Body = []byte("405 method not allowed") - defaultAppEngine bool + default404Body = []byte("404 page not found") + default405Body = []byte("405 method not allowed") ) +var defaultAppEngine bool + // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) diff --git a/mode.go b/mode.go index ca3677a..11f833e 100644 --- a/mode.go +++ b/mode.go @@ -22,6 +22,7 @@ const ( // TestMode indicates gin mode is test. TestMode = "test" ) + const ( debugCode = iota releaseCode diff --git a/path.go b/path.go index 51346e4..d42d6b9 100644 --- a/path.go +++ b/path.go @@ -136,10 +136,11 @@ func bufApp(buf *[]byte, s string, w int, c byte) { // Otherwise use either the stack buffer, if it is large enough, or // allocate a new buffer on the heap, and copy all previous characters. - if l := len(s); l > cap(b) { - *buf = make([]byte, len(s)) + length := len(s) + if length > cap(b) { + *buf = make([]byte, length) } else { - *buf = (*buf)[:l] + *buf = (*buf)[:length] } b = *buf diff --git a/recovery.go b/recovery.go index bc946c0..8cf0932 100644 --- a/recovery.go +++ b/recovery.go @@ -146,6 +146,6 @@ func function(pc uintptr) []byte { } func timeFormat(t time.Time) string { - var timeString = t.Format("2006/01/02 - 15:04:05") + timeString := t.Format("2006/01/02 - 15:04:05") return timeString } From 6ac7f194c4e4867f30c94674af3015fceb1a97af Mon Sep 17 00:00:00 2001 From: thinkerou Date: Tue, 5 May 2020 13:55:57 +0800 Subject: [PATCH 474/912] chore: update some code style (#2356) --- context.go | 2 +- gin.go | 8 ++++---- githubapi_test.go | 8 ++++---- internal/json/jsoniter.go | 2 +- routes_test.go | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/context.go b/context.go index 01cff1a..4ebcc29 100644 --- a/context.go +++ b/context.go @@ -865,7 +865,7 @@ func (c *Context) IndentedJSON(code int, obj interface{}) { // Default prepends "while(1)," to response body if the given struct is array values. // It also sets the Content-Type as "application/json". func (c *Context) SecureJSON(code int, obj interface{}) { - c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj}) + c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj}) } // JSONP serializes the given struct as JSON into the response body. diff --git a/gin.go b/gin.go index 888be36..4e72f81 100644 --- a/gin.go +++ b/gin.go @@ -104,7 +104,7 @@ type Engine struct { RemoveExtraSlash bool delims render.Delims - secureJsonPrefix string + secureJSONPrefix string HTMLRender render.HTMLRender FuncMap template.FuncMap allNoRoute HandlersChain @@ -145,7 +145,7 @@ func New() *Engine { MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, - secureJsonPrefix: "while(1);", + secureJSONPrefix: "while(1);", } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { @@ -172,9 +172,9 @@ func (engine *Engine) Delims(left, right string) *Engine { return engine } -// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON. +// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON. func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { - engine.secureJsonPrefix = prefix + engine.secureJSONPrefix = prefix return engine } diff --git a/githubapi_test.go b/githubapi_test.go index 925c5a1..3f440bc 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -291,13 +291,13 @@ func TestShouldBindUri(t *testing.T) { type Person struct { Name string `uri:"name" binding:"required"` - Id string `uri:"id" binding:"required"` + ID string `uri:"id" binding:"required"` } router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person assert.NoError(t, c.ShouldBindUri(&person)) assert.True(t, "" != person.Name) - assert.True(t, "" != person.Id) + assert.True(t, "" != person.ID) c.String(http.StatusOK, "ShouldBindUri test OK") }) @@ -313,13 +313,13 @@ func TestBindUri(t *testing.T) { type Person struct { Name string `uri:"name" binding:"required"` - Id string `uri:"id" binding:"required"` + ID string `uri:"id" binding:"required"` } router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person assert.NoError(t, c.BindUri(&person)) assert.True(t, "" != person.Name) - assert.True(t, "" != person.Id) + assert.True(t, "" != person.ID) c.String(http.StatusOK, "BindUri test OK") }) diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index fabd7b8..649a3cd 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -6,7 +6,7 @@ package json -import "github.com/json-iterator/go" +import jsoniter "github.com/json-iterator/go" var ( json = jsoniter.ConfigCompatibleWithStandardLibrary diff --git a/routes_test.go b/routes_test.go index ee6ea82..360ade6 100644 --- a/routes_test.go +++ b/routes_test.go @@ -514,7 +514,7 @@ func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) { // Middleware must be called just only once by per request. middlewareCalledNum := 0 router.Use(func(c *Context) { - middlewareCalledNum += 1 + middlewareCalledNum++ }) router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) From 747efffd2a4571681ed227ef415945a7e5713bc3 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Tue, 5 May 2020 14:06:42 +0800 Subject: [PATCH 475/912] chore: update godoc address (#2357) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9dde693..dae63e2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) -[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) +[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) From 05464a8f6b8068d488660dd2b0c767e95810156f Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 5 May 2020 16:37:40 +0800 Subject: [PATCH 476/912] docs: update benchmark result v1.6.3 (#2355) --- BENCHMARKS.md | 1283 ++++++++++++++++++++++++------------------------- README.md | 64 +-- 2 files changed, 663 insertions(+), 684 deletions(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index e4ff467..b164ae0 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -1,693 +1,666 @@ -## Benchmark System +# Benchmark System -**VM HOST:** DigitalOcean -**Machine:** 12 CPU, 24 GB RAM. Ubuntu 16.04.2 x64 -**Date:** Nov 26th, 2019 -**Go Version:** 1.13.4 linux/amd64 -**Source:** [Go HTTP Router Benchmark](https://github.com/julienschmidt/go-http-routing-benchmark) -**Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) +**VM HOST:** Travis +**Machine:** Ubuntu 16.04.6 LTS x64 +**Date:** May 04th, 2020 +**Version:** Gin v1.6.3 +**Go Version:** 1.14.2 linux/amd64 +**Source:** [Go HTTP Router Benchmark](https://github/gin-gonic/go-http-routing-benchmark) +**Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061) ## Static Routes: 157 -``` -Gin: 34936 Bytes - -HttpServeMux: 14512 Bytes -Ace: 30648 Bytes -Aero: 800696 Bytes -Bear: 30664 Bytes -Beego: 98456 Bytes -Bone: 40224 Bytes -Chi: 83608 Bytes -CloudyKitRouter: 30448 Bytes -Denco: 9928 Bytes -Echo: 76584 Bytes -GocraftWeb: 55496 Bytes -Goji: 29744 Bytes -Gojiv2: 105840 Bytes -GoJsonRest: 137512 Bytes -GoRestful: 816936 Bytes -GorillaMux: 585632 Bytes -GowwwRouter: 24968 Bytes -HttpRouter: 21680 Bytes -HttpTreeMux: 73448 Bytes -Kocha: 115472 Bytes -LARS: 30640 Bytes -Macaron: 38592 Bytes -Martini: 310864 Bytes -Pat: 19696 Bytes -Possum: 89920 Bytes -R2router: 23712 Bytes -Rivet: 24608 Bytes -Tango: 28264 Bytes -TigerTonic: 78768 Bytes -Traffic: 538976 Bytes -Vulcan: 369960 Bytes +```sh +Gin: 34936 Bytes + +HttpServeMux: 14512 Bytes +Ace: 30680 Bytes +Aero: 34536 Bytes +Bear: 30456 Bytes +Beego: 98456 Bytes +Bone: 40224 Bytes +Chi: 83608 Bytes +Denco: 10216 Bytes +Echo: 80328 Bytes +GocraftWeb: 55288 Bytes +Goji: 29744 Bytes +Gojiv2: 105840 Bytes +GoJsonRest: 137496 Bytes +GoRestful: 816936 Bytes +GorillaMux: 585632 Bytes +GowwwRouter: 24968 Bytes +HttpRouter: 21712 Bytes +HttpTreeMux: 73448 Bytes +Kocha: 115472 Bytes +LARS: 30640 Bytes +Macaron: 38592 Bytes +Martini: 310864 Bytes +Pat: 19696 Bytes +Possum: 89920 Bytes +R2router: 23712 Bytes +Rivet: 24608 Bytes +Tango: 28264 Bytes +TigerTonic: 78768 Bytes +Traffic: 538976 Bytes +Vulcan: 369960 Bytes ``` ## GithubAPI Routes: 203 -``` -Gin: 58512 Bytes - -Ace: 48640 Bytes -Aero: 1386208 Bytes -Bear: 82536 Bytes -Beego: 150936 Bytes -Bone: 100976 Bytes -Chi: 95112 Bytes -CloudyKitRouter: 93704 Bytes -Denco: 36736 Bytes -Echo: 96328 Bytes -GocraftWeb: 95432 Bytes -Goji: 51600 Bytes -Gojiv2: 104704 Bytes -GoJsonRest: 142024 Bytes -GoRestful: 1241656 Bytes -GorillaMux: 1322784 Bytes -GowwwRouter: 80008 Bytes -HttpRouter: 37096 Bytes -HttpTreeMux: 78800 Bytes -Kocha: 785408 Bytes -LARS: 48600 Bytes -Macaron: 93680 Bytes -Martini: 485264 Bytes -Pat: 21200 Bytes -Possum: 85312 Bytes -R2router: 47104 Bytes -Rivet: 42840 Bytes -Tango: 54840 Bytes -TigerTonic: 96176 Bytes -Traffic: 921744 Bytes -Vulcan: 425368 Bytes +```sh +Gin: 58512 Bytes + +Ace: 48688 Bytes +Aero: 318568 Bytes +Bear: 84248 Bytes +Beego: 150936 Bytes +Bone: 100976 Bytes +Chi: 95112 Bytes +Denco: 36736 Bytes +Echo: 100296 Bytes +GocraftWeb: 95432 Bytes +Goji: 49680 Bytes +Gojiv2: 104704 Bytes +GoJsonRest: 141976 Bytes +GoRestful: 1241656 Bytes +GorillaMux: 1322784 Bytes +GowwwRouter: 80008 Bytes +HttpRouter: 37144 Bytes +HttpTreeMux: 78800 Bytes +Kocha: 785120 Bytes +LARS: 48600 Bytes +Macaron: 92784 Bytes +Martini: 485264 Bytes +Pat: 21200 Bytes +Possum: 85312 Bytes +R2router: 47104 Bytes +Rivet: 42840 Bytes +Tango: 54840 Bytes +TigerTonic: 95264 Bytes +Traffic: 921744 Bytes +Vulcan: 425992 Bytes ``` ## GPlusAPI Routes: 13 -``` -Gin: 4384 Bytes - -Ace: 3 664 Bytes -Aero: 88248 Bytes -Bear: 7112 Bytes -Beego: 10272 Bytes -Bone: 6688 Bytes -Chi: 8024 Bytes -CloudyKitRouter: 6728 Bytes -Denco: 3264 Bytes -Echo: 9272 Bytes -GocraftWeb: 7496 Bytes -Goji: 3152 Bytes -Gojiv2: 7376 Bytes -GoJsonRest: 11416 Bytes -GoRestful: 74328 Bytes -GorillaMux: 66208 Bytes -GowwwRouter: 5744 Bytes -HttpRouter: 2760 Bytes -HttpTreeMux: 7440 Bytes -Kocha: 128880 Bytes -LARS: 3656 Bytes -Macaron: 8656 Bytes -Martini: 23920 Bytes -Pat: 1856 Bytes -Possum: 7248 Bytes -R2router: 3928 Bytes -Rivet: 3064 Bytes -Tango: 5168 Bytes -TigerTonic: 9408 Bytes -Traffic: 46400 Bytes -Vulcan: 25544 Bytes +```sh +Gin: 4384 Bytes + +Ace: 3712 Bytes +Aero: 26056 Bytes +Bear: 7112 Bytes +Beego: 10272 Bytes +Bone: 6688 Bytes +Chi: 8024 Bytes +Denco: 3264 Bytes +Echo: 9688 Bytes +GocraftWeb: 7496 Bytes +Goji: 3152 Bytes +Gojiv2: 7376 Bytes +GoJsonRest: 11400 Bytes +GoRestful: 74328 Bytes +GorillaMux: 66208 Bytes +GowwwRouter: 5744 Bytes +HttpRouter: 2808 Bytes +HttpTreeMux: 7440 Bytes +Kocha: 128880 Bytes +LARS: 3656 Bytes +Macaron: 8656 Bytes +Martini: 23920 Bytes +Pat: 1856 Bytes +Possum: 7248 Bytes +R2router: 3928 Bytes +Rivet: 3064 Bytes +Tango: 5168 Bytes +TigerTonic: 9408 Bytes +Traffic: 46400 Bytes +Vulcan: 25544 Bytes ``` ## ParseAPI Routes: 26 -``` -Gin: 7776 Bytes - -Ace: 6656 Bytes -Aero: 163736 Bytes -Bear: 12528 Bytes -Beego: 19280 Bytes -Bone: 11440 Bytes -Chi: 9744 Bytes -Denco: 4192 Bytes -Echo: 11648 Bytes -GocraftWeb: 12800 Bytes -Goji: 5680 Bytes -Gojiv2: 14464 Bytes -GoJsonRest: 14424 Bytes -GoRestful: 116264 Bytes -GorillaMux: 105880 Bytes -GowwwRouter: 9344 Bytes -HttpRouter: 5024 Bytes -HttpTreeMux: 7848 Bytes -Kocha: 181712 Bytes -LARS: 6632 Bytes -Macaron: 13648 Bytes -Martini: 45888 Bytes -Pat: 2560 Bytes -Possum: 9200 Bytes -R2router: 7056 Bytes -Rivet: 5680 Bytes -Tango: 8920 Bytes -TigerTonic: 9840 Bytes -Traffic: 79096 Bytes -Vulcan: 44504 Bytes +```sh +Gin: 7776 Bytes + +Ace: 6704 Bytes +Aero: 28488 Bytes +Bear: 12320 Bytes +Beego: 19280 Bytes +Bone: 11440 Bytes +Chi: 9744 Bytes +Denco: 4192 Bytes +Echo: 11664 Bytes +GocraftWeb: 12800 Bytes +Goji: 5680 Bytes +Gojiv2: 14464 Bytes +GoJsonRest: 14072 Bytes +GoRestful: 116264 Bytes +GorillaMux: 105880 Bytes +GowwwRouter: 9344 Bytes +HttpRouter: 5072 Bytes +HttpTreeMux: 7848 Bytes +Kocha: 181712 Bytes +LARS: 6632 Bytes +Macaron: 13648 Bytes +Martini: 45888 Bytes +Pat: 2560 Bytes +Possum: 9200 Bytes +R2router: 7056 Bytes +Rivet: 5680 Bytes +Tango: 8920 Bytes +TigerTonic: 9840 Bytes +Traffic: 79096 Bytes +Vulcan: 44504 Bytes ``` ## Static Routes -``` -BenchmarkGin_StaticAll 25604 45487 ns/op 0 B/op 0 allocs/op - -BenchmarkAce_StaticAll 28402 42046 ns/op 0 B/op 0 allocs/op -BenchmarkAero_StaticAll 38766 30333 ns/op 0 B/op 0 allocs/op -BenchmarkHttpServeMux_StaticAll 25728 46511 ns/op 0 B/op 0 allocs/op -BenchmarkBeego_StaticAll 5098 288527 ns/op 55264 B/op 471 allocs/op -BenchmarkBear_StaticAll 10000 126323 ns/op 20272 B/op 469 allocs/op -BenchmarkBone_StaticAll 9499 113631 ns/op 0 B/op 0 allocs/op -BenchmarkChi_StaticAll 7912 237363 ns/op 67824 B/op 471 allocs/op -BenchmarkCloudyKitRouter_StaticAll 41626 28668 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_StaticAll 95774 12221 ns/op 0 B/op 0 allocs/op -BenchmarkEcho_StaticAll 26246 44603 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_StaticAll 10000 193337 ns/op 46312 B/op 785 allocs/op -BenchmarkGoji_StaticAll 15886 75789 ns/op 0 B/op 0 allocs/op -BenchmarkGojiv2_StaticAll 1886 597374 ns/op 205984 B/op 1570 allocs/op -BenchmarkGoJsonRest_StaticAll 4700 307144 ns/op 51653 B/op 1727 allocs/op -BenchmarkGoRestful_StaticAll 429 2880165 ns/op 613280 B/op 2053 allocs/op -BenchmarkGorillaMux_StaticAll 754 1491761 ns/op 153233 B/op 1413 allocs/op -BenchmarkGowwwRouter_StaticAll 28071 42629 ns/op 0 B/op 0 allocs/op -BenchmarkHttpRouter_StaticAll 47672 24875 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_StaticAll 46770 25100 ns/op 0 B/op 0 allocs/op -BenchmarkKocha_StaticAll 61045 19494 ns/op 0 B/op 0 allocs/op -BenchmarkLARS_StaticAll 36103 32700 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_StaticAll 4261 430131 ns/op 115552 B/op 1256 allocs/op -BenchmarkMartini_StaticAll 481 2320157 ns/op 125444 B/op 1717 allocs/op -BenchmarkPat_StaticAll 325 3739521 ns/op 602832 B/op 12559 allocs/op -BenchmarkPossum_StaticAll 10000 203575 ns/op 65312 B/op 471 allocs/op -BenchmarkR2router_StaticAll 10000 110536 ns/op 22608 B/op 628 allocs/op -BenchmarkRivet_StaticAll 23344 51174 ns/op 0 B/op 0 allocs/op -BenchmarkTango_StaticAll 3596 340045 ns/op 39209 B/op 1256 allocs/op -BenchmarkTigerTonic_StaticAll 16784 71807 ns/op 7376 B/op 157 allocs/op -BenchmarkTraffic_StaticAll 350 3435155 ns/op 754862 B/op 14601 allocs/op -BenchmarkVulcan_StaticAll 5930 200284 ns/op 15386 B/op 471 allocs/op +```sh +BenchmarkGin_StaticAll 62169 19319 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_StaticAll 65428 18313 ns/op 0 B/op 0 allocs/op +BenchmarkAero_StaticAll 121132 9632 ns/op 0 B/op 0 allocs/op +BenchmarkHttpServeMux_StaticAll 52626 22758 ns/op 0 B/op 0 allocs/op +BenchmarkBeego_StaticAll 9962 179058 ns/op 55264 B/op 471 allocs/op +BenchmarkBear_StaticAll 14894 80966 ns/op 20272 B/op 469 allocs/op +BenchmarkBone_StaticAll 18718 64065 ns/op 0 B/op 0 allocs/op +BenchmarkChi_StaticAll 10000 149827 ns/op 67824 B/op 471 allocs/op +BenchmarkDenco_StaticAll 211393 5680 ns/op 0 B/op 0 allocs/op +BenchmarkEcho_StaticAll 49341 24343 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_StaticAll 10000 126209 ns/op 46312 B/op 785 allocs/op +BenchmarkGoji_StaticAll 27956 43174 ns/op 0 B/op 0 allocs/op +BenchmarkGojiv2_StaticAll 3430 370718 ns/op 205984 B/op 1570 allocs/op +BenchmarkGoJsonRest_StaticAll 9134 188888 ns/op 51653 B/op 1727 allocs/op +BenchmarkGoRestful_StaticAll 706 1703330 ns/op 613280 B/op 2053 allocs/op +BenchmarkGorillaMux_StaticAll 1268 924083 ns/op 153233 B/op 1413 allocs/op +BenchmarkGowwwRouter_StaticAll 63374 18935 ns/op 0 B/op 0 allocs/op +BenchmarkHttpRouter_StaticAll 109938 10902 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_StaticAll 109166 10861 ns/op 0 B/op 0 allocs/op +BenchmarkKocha_StaticAll 92258 12992 ns/op 0 B/op 0 allocs/op +BenchmarkLARS_StaticAll 65200 18387 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_StaticAll 5671 291501 ns/op 115553 B/op 1256 allocs/op +BenchmarkMartini_StaticAll 807 1460498 ns/op 125444 B/op 1717 allocs/op +BenchmarkPat_StaticAll 513 2342396 ns/op 602832 B/op 12559 allocs/op +BenchmarkPossum_StaticAll 10000 128270 ns/op 65312 B/op 471 allocs/op +BenchmarkR2router_StaticAll 16726 71760 ns/op 22608 B/op 628 allocs/op +BenchmarkRivet_StaticAll 41722 28723 ns/op 0 B/op 0 allocs/op +BenchmarkTango_StaticAll 7606 205082 ns/op 39209 B/op 1256 allocs/op +BenchmarkTigerTonic_StaticAll 26247 45806 ns/op 7376 B/op 157 allocs/op +BenchmarkTraffic_StaticAll 550 2284518 ns/op 754864 B/op 14601 allocs/op +BenchmarkVulcan_StaticAll 10000 131343 ns/op 15386 B/op 471 allocs/op ``` ## Micro Benchmarks -``` -BenchmarkGin_Param 8623915 139 ns/op 0 B/op 0 allocs/op - -BenchmarkAce_Param 3976539 290 ns/op 32 B/op 1 allocs/op -BenchmarkAero_Param 8948976 133 ns/op 0 B/op 0 allocs/op -BenchmarkBear_Param 1000000 1277 ns/op 456 B/op 5 allocs/op -BenchmarkBeego_Param 889404 1785 ns/op 352 B/op 3 allocs/op -BenchmarkBone_Param 1000000 2219 ns/op 816 B/op 6 allocs/op -BenchmarkChi_Param 1000000 1386 ns/op 432 B/op 3 allocs/op -BenchmarkCloudyKitRouter_Param 18343244 61.2 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_Param 5637424 204 ns/op 32 B/op 1 allocs/op -BenchmarkEcho_Param 9540910 122 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_Param 1000000 1939 ns/op 648 B/op 8 allocs/op -BenchmarkGoji_Param 1283509 938 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_Param 331266 3554 ns/op 1328 B/op 11 allocs/op -BenchmarkGoJsonRest_Param 908851 2158 ns/op 649 B/op 13 allocs/op -BenchmarkGoRestful_Param 135781 9339 ns/op 4192 B/op 14 allocs/op -BenchmarkGorillaMux_Param 308407 3893 ns/op 1280 B/op 10 allocs/op -BenchmarkGowwwRouter_Param 1000000 1044 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_Param 6653476 162 ns/op 32 B/op 1 allocs/op -BenchmarkHttpTreeMux_Param 1361378 819 ns/op 352 B/op 3 allocs/op -BenchmarkKocha_Param 3084330 353 ns/op 56 B/op 3 allocs/op -BenchmarkLARS_Param 11502079 107 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_Param 439095 3750 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_Param 177099 7479 ns/op 1072 B/op 10 allocs/op -BenchmarkPat_Param 729747 2048 ns/op 536 B/op 11 allocs/op -BenchmarkPossum_Param 995989 1705 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_Param 1000000 1037 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_Param 4057065 271 ns/op 48 B/op 1 allocs/op -BenchmarkTango_Param 812029 1682 ns/op 248 B/op 8 allocs/op -BenchmarkTigerTonic_Param 450592 3358 ns/op 776 B/op 16 allocs/op -BenchmarkTraffic_Param 206390 5661 ns/op 1856 B/op 21 allocs/op -BenchmarkVulcan_Param 1441147 792 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_Param5 1891473 632 ns/op 160 B/op 1 allocs/op -BenchmarkAero_Param5 5191258 227 ns/op 0 B/op 0 allocs/op -BenchmarkBear_Param5 988882 1734 ns/op 501 B/op 5 allocs/op -BenchmarkBeego_Param5 625438 2132 ns/op 352 B/op 3 allocs/op -BenchmarkBone_Param5 622030 3061 ns/op 864 B/op 6 allocs/op -BenchmarkChi_Param5 1000000 1735 ns/op 432 B/op 3 allocs/op -BenchmarkCloudyKitRouter_Param5 5167868 225 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_Param5 2174550 550 ns/op 160 B/op 1 allocs/op -BenchmarkEcho_Param5 4272258 275 ns/op 0 B/op 0 allocs/op -BenchmarkGin_Param5 4190391 275 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_Param5 623739 3107 ns/op 920 B/op 11 allocs/op -BenchmarkGoji_Param5 1000000 1310 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_Param5 314694 3803 ns/op 1392 B/op 11 allocs/op -BenchmarkGoJsonRest_Param5 308203 4108 ns/op 1097 B/op 16 allocs/op -BenchmarkGoRestful_Param5 115048 9787 ns/op 4288 B/op 14 allocs/op -BenchmarkGorillaMux_Param5 180812 5658 ns/op 1344 B/op 10 allocs/op -BenchmarkGowwwRouter_Param5 1000000 1156 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_Param5 2395767 502 ns/op 160 B/op 1 allocs/op -BenchmarkHttpTreeMux_Param5 899263 2096 ns/op 576 B/op 6 allocs/op -BenchmarkKocha_Param5 1000000 1639 ns/op 440 B/op 10 allocs/op -BenchmarkLARS_Param5 5807994 203 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_Param5 272967 4087 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_Param5 120735 8886 ns/op 1232 B/op 11 allocs/op -BenchmarkPat_Param5 294714 4943 ns/op 888 B/op 29 allocs/op -BenchmarkPossum_Param5 1000000 1689 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_Param5 1000000 1319 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_Param5 1347289 883 ns/op 240 B/op 1 allocs/op -BenchmarkTango_Param5 617077 2091 ns/op 360 B/op 8 allocs/op -BenchmarkTigerTonic_Param5 113659 11212 ns/op 2279 B/op 39 allocs/op -BenchmarkTraffic_Param5 134148 9039 ns/op 2208 B/op 27 allocs/op -BenchmarkVulcan_Param5 1000000 1095 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_Param20 1000000 1838 ns/op 640 B/op 1 allocs/op -BenchmarkAero_Param20 17120668 66.1 ns/op 0 B/op 0 allocs/op -BenchmarkBear_Param20 205585 5332 ns/op 1665 B/op 5 allocs/op -BenchmarkBeego_Param20 230522 5382 ns/op 352 B/op 3 allocs/op -BenchmarkBone_Param20 167190 8076 ns/op 2031 B/op 6 allocs/op -BenchmarkChi_Param20 480528 3044 ns/op 432 B/op 3 allocs/op -BenchmarkCloudyKitRouter_Param20 1347794 872 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_Param20 1000000 1867 ns/op 640 B/op 1 allocs/op -BenchmarkEcho_Param20 1363526 897 ns/op 0 B/op 0 allocs/op -BenchmarkGin_Param20 1607217 748 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_Param20 97314 11671 ns/op 3795 B/op 15 allocs/op -BenchmarkGoji_Param20 289407 4220 ns/op 1246 B/op 2 allocs/op -BenchmarkGojiv2_Param20 245186 4869 ns/op 1632 B/op 11 allocs/op -BenchmarkGoJsonRest_Param20 78049 15725 ns/op 4485 B/op 20 allocs/op -BenchmarkGoRestful_Param20 66907 18031 ns/op 6716 B/op 18 allocs/op -BenchmarkGorillaMux_Param20 81866 12422 ns/op 3452 B/op 12 allocs/op -BenchmarkGowwwRouter_Param20 955983 1688 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_Param20 1000000 1629 ns/op 640 B/op 1 allocs/op -BenchmarkHttpTreeMux_Param20 108940 10241 ns/op 3195 B/op 10 allocs/op -BenchmarkKocha_Param20 197022 5488 ns/op 1808 B/op 27 allocs/op -BenchmarkLARS_Param20 2451241 490 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_Param20 106770 10788 ns/op 2923 B/op 12 allocs/op -BenchmarkMartini_Param20 69028 17112 ns/op 3596 B/op 13 allocs/op -BenchmarkPat_Param20 56275 21535 ns/op 4424 B/op 93 allocs/op -BenchmarkPossum_Param20 1000000 1705 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_Param20 172215 7099 ns/op 2283 B/op 7 allocs/op -BenchmarkRivet_Param20 447265 2987 ns/op 1024 B/op 1 allocs/op -BenchmarkTango_Param20 327494 3850 ns/op 856 B/op 8 allocs/op -BenchmarkTigerTonic_Param20 27176 44571 ns/op 9871 B/op 119 allocs/op -BenchmarkTraffic_Param20 38828 31025 ns/op 7856 B/op 47 allocs/op -BenchmarkVulcan_Param20 560442 1807 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_ParamWrite 2712150 442 ns/op 40 B/op 2 allocs/op -BenchmarkAero_ParamWrite 6392880 189 ns/op 0 B/op 0 allocs/op -BenchmarkBear_ParamWrite 1000000 1338 ns/op 456 B/op 5 allocs/op -BenchmarkBeego_ParamWrite 821431 1886 ns/op 360 B/op 4 allocs/op -BenchmarkBone_ParamWrite 913227 2350 ns/op 816 B/op 6 allocs/op -BenchmarkChi_ParamWrite 1000000 1427 ns/op 432 B/op 3 allocs/op -BenchmarkCloudyKitRouter_ParamWrite 18645724 60.9 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_ParamWrite 4394764 264 ns/op 32 B/op 1 allocs/op -BenchmarkEcho_ParamWrite 5288883 242 ns/op 8 B/op 1 allocs/op -BenchmarkGin_ParamWrite 4584932 253 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_ParamWrite 866242 2094 ns/op 656 B/op 9 allocs/op -BenchmarkGoji_ParamWrite 1201875 1004 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_ParamWrite 317766 3777 ns/op 1360 B/op 13 allocs/op -BenchmarkGoJsonRest_ParamWrite 380242 3447 ns/op 1128 B/op 18 allocs/op -BenchmarkGoRestful_ParamWrite 131046 9340 ns/op 4200 B/op 15 allocs/op -BenchmarkGorillaMux_ParamWrite 298428 3970 ns/op 1280 B/op 10 allocs/op -BenchmarkGowwwRouter_ParamWrite 655940 2744 ns/op 976 B/op 8 allocs/op -BenchmarkHttpRouter_ParamWrite 5237014 219 ns/op 32 B/op 1 allocs/op -BenchmarkHttpTreeMux_ParamWrite 1379904 853 ns/op 352 B/op 3 allocs/op -BenchmarkKocha_ParamWrite 2939042 400 ns/op 56 B/op 3 allocs/op -BenchmarkLARS_ParamWrite 6181642 199 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_ParamWrite 352497 4670 ns/op 1176 B/op 14 allocs/op -BenchmarkMartini_ParamWrite 138259 8543 ns/op 1176 B/op 14 allocs/op -BenchmarkPat_ParamWrite 552386 3262 ns/op 960 B/op 15 allocs/op -BenchmarkPossum_ParamWrite 1000000 1711 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_ParamWrite 1000000 1085 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_ParamWrite 2374513 489 ns/op 112 B/op 2 allocs/op -BenchmarkTango_ParamWrite 1443907 812 ns/op 136 B/op 4 allocs/op -BenchmarkTigerTonic_ParamWrite 324264 4874 ns/op 1216 B/op 21 allocs/op -BenchmarkTraffic_ParamWrite 170726 7155 ns/op 2280 B/op 25 allocs/op -BenchmarkVulcan_ParamWrite 1498888 776 ns/op 98 B/op 3 allocs/op - +```sh +BenchmarkGin_Param 18785022 63.9 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_Param 14689765 81.5 ns/op 0 B/op 0 allocs/op +BenchmarkAero_Param 23094770 51.2 ns/op 0 B/op 0 allocs/op +BenchmarkBear_Param 1417045 845 ns/op 456 B/op 5 allocs/op +BenchmarkBeego_Param 1000000 1080 ns/op 352 B/op 3 allocs/op +BenchmarkBone_Param 1000000 1463 ns/op 816 B/op 6 allocs/op +BenchmarkChi_Param 1378756 885 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_Param 8557899 143 ns/op 32 B/op 1 allocs/op +BenchmarkEcho_Param 16433347 75.5 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_Param 1000000 1218 ns/op 648 B/op 8 allocs/op +BenchmarkGoji_Param 1921248 617 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_Param 561848 2156 ns/op 1328 B/op 11 allocs/op +BenchmarkGoJsonRest_Param 1000000 1358 ns/op 649 B/op 13 allocs/op +BenchmarkGoRestful_Param 224857 5307 ns/op 4192 B/op 14 allocs/op +BenchmarkGorillaMux_Param 498313 2459 ns/op 1280 B/op 10 allocs/op +BenchmarkGowwwRouter_Param 1864354 654 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_Param 26269074 47.7 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_Param 2109829 557 ns/op 352 B/op 3 allocs/op +BenchmarkKocha_Param 5050216 243 ns/op 56 B/op 3 allocs/op +BenchmarkLARS_Param 19811712 59.9 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_Param 662746 2329 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_Param 279902 4260 ns/op 1072 B/op 10 allocs/op +BenchmarkPat_Param 1000000 1382 ns/op 536 B/op 11 allocs/op +BenchmarkPossum_Param 1000000 1014 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_Param 1712559 707 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_Param 6648086 182 ns/op 48 B/op 1 allocs/op +BenchmarkTango_Param 1221504 994 ns/op 248 B/op 8 allocs/op +BenchmarkTigerTonic_Param 891661 2261 ns/op 776 B/op 16 allocs/op +BenchmarkTraffic_Param 350059 3598 ns/op 1856 B/op 21 allocs/op +BenchmarkVulcan_Param 2517823 472 ns/op 98 B/op 3 allocs/op +BenchmarkAce_Param5 9214365 130 ns/op 0 B/op 0 allocs/op +BenchmarkAero_Param5 15369013 77.9 ns/op 0 B/op 0 allocs/op +BenchmarkBear_Param5 1000000 1113 ns/op 501 B/op 5 allocs/op +BenchmarkBeego_Param5 1000000 1269 ns/op 352 B/op 3 allocs/op +BenchmarkBone_Param5 986820 1873 ns/op 864 B/op 6 allocs/op +BenchmarkChi_Param5 1000000 1156 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_Param5 3036331 400 ns/op 160 B/op 1 allocs/op +BenchmarkEcho_Param5 6447133 186 ns/op 0 B/op 0 allocs/op +BenchmarkGin_Param5 10786068 110 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_Param5 844820 1944 ns/op 920 B/op 11 allocs/op +BenchmarkGoji_Param5 1474965 827 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_Param5 442820 2516 ns/op 1392 B/op 11 allocs/op +BenchmarkGoJsonRest_Param5 507555 2711 ns/op 1097 B/op 16 allocs/op +BenchmarkGoRestful_Param5 216481 6093 ns/op 4288 B/op 14 allocs/op +BenchmarkGorillaMux_Param5 314402 3628 ns/op 1344 B/op 10 allocs/op +BenchmarkGowwwRouter_Param5 1624660 733 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_Param5 13167324 92.0 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_Param5 1000000 1295 ns/op 576 B/op 6 allocs/op +BenchmarkKocha_Param5 1000000 1138 ns/op 440 B/op 10 allocs/op +BenchmarkLARS_Param5 11580613 105 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_Param5 473596 2755 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_Param5 230756 5111 ns/op 1232 B/op 11 allocs/op +BenchmarkPat_Param5 469190 3370 ns/op 888 B/op 29 allocs/op +BenchmarkPossum_Param5 1000000 1002 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_Param5 1422129 844 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_Param5 2263789 539 ns/op 240 B/op 1 allocs/op +BenchmarkTango_Param5 1000000 1256 ns/op 360 B/op 8 allocs/op +BenchmarkTigerTonic_Param5 175500 7492 ns/op 2279 B/op 39 allocs/op +BenchmarkTraffic_Param5 233631 5816 ns/op 2208 B/op 27 allocs/op +BenchmarkVulcan_Param5 1923416 629 ns/op 98 B/op 3 allocs/op +BenchmarkAce_Param20 4321266 281 ns/op 0 B/op 0 allocs/op +BenchmarkAero_Param20 31501641 35.2 ns/op 0 B/op 0 allocs/op +BenchmarkBear_Param20 335204 3489 ns/op 1665 B/op 5 allocs/op +BenchmarkBeego_Param20 503674 2860 ns/op 352 B/op 3 allocs/op +BenchmarkBone_Param20 298922 4741 ns/op 2031 B/op 6 allocs/op +BenchmarkChi_Param20 878181 1957 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_Param20 1000000 1360 ns/op 640 B/op 1 allocs/op +BenchmarkEcho_Param20 2104946 580 ns/op 0 B/op 0 allocs/op +BenchmarkGin_Param20 4167204 290 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_Param20 173064 7514 ns/op 3796 B/op 15 allocs/op +BenchmarkGoji_Param20 458778 2651 ns/op 1247 B/op 2 allocs/op +BenchmarkGojiv2_Param20 364862 3178 ns/op 1632 B/op 11 allocs/op +BenchmarkGoJsonRest_Param20 125514 9760 ns/op 4485 B/op 20 allocs/op +BenchmarkGoRestful_Param20 101217 11964 ns/op 6715 B/op 18 allocs/op +BenchmarkGorillaMux_Param20 147654 8132 ns/op 3452 B/op 12 allocs/op +BenchmarkGowwwRouter_Param20 1000000 1225 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_Param20 4920895 247 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_Param20 173202 6605 ns/op 3196 B/op 10 allocs/op +BenchmarkKocha_Param20 345988 3620 ns/op 1808 B/op 27 allocs/op +BenchmarkLARS_Param20 4592326 262 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_Param20 166492 7286 ns/op 2924 B/op 12 allocs/op +BenchmarkMartini_Param20 122162 10653 ns/op 3595 B/op 13 allocs/op +BenchmarkPat_Param20 78630 15239 ns/op 4424 B/op 93 allocs/op +BenchmarkPossum_Param20 1000000 1008 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_Param20 294981 4587 ns/op 2284 B/op 7 allocs/op +BenchmarkRivet_Param20 691798 2090 ns/op 1024 B/op 1 allocs/op +BenchmarkTango_Param20 842440 2505 ns/op 856 B/op 8 allocs/op +BenchmarkTigerTonic_Param20 38614 31509 ns/op 9870 B/op 119 allocs/op +BenchmarkTraffic_Param20 57633 21107 ns/op 7853 B/op 47 allocs/op +BenchmarkVulcan_Param20 1000000 1178 ns/op 98 B/op 3 allocs/op +BenchmarkAce_ParamWrite 7330743 180 ns/op 8 B/op 1 allocs/op +BenchmarkAero_ParamWrite 13833598 86.7 ns/op 0 B/op 0 allocs/op +BenchmarkBear_ParamWrite 1363321 867 ns/op 456 B/op 5 allocs/op +BenchmarkBeego_ParamWrite 1000000 1104 ns/op 360 B/op 4 allocs/op +BenchmarkBone_ParamWrite 1000000 1475 ns/op 816 B/op 6 allocs/op +BenchmarkChi_ParamWrite 1320590 892 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_ParamWrite 7093605 172 ns/op 32 B/op 1 allocs/op +BenchmarkEcho_ParamWrite 8434424 161 ns/op 8 B/op 1 allocs/op +BenchmarkGin_ParamWrite 10377034 118 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_ParamWrite 1000000 1266 ns/op 656 B/op 9 allocs/op +BenchmarkGoji_ParamWrite 1874168 654 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_ParamWrite 459032 2352 ns/op 1360 B/op 13 allocs/op +BenchmarkGoJsonRest_ParamWrite 499434 2145 ns/op 1128 B/op 18 allocs/op +BenchmarkGoRestful_ParamWrite 241087 5470 ns/op 4200 B/op 15 allocs/op +BenchmarkGorillaMux_ParamWrite 425686 2522 ns/op 1280 B/op 10 allocs/op +BenchmarkGowwwRouter_ParamWrite 922172 1778 ns/op 976 B/op 8 allocs/op +BenchmarkHttpRouter_ParamWrite 15392049 77.7 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_ParamWrite 1973385 597 ns/op 352 B/op 3 allocs/op +BenchmarkKocha_ParamWrite 4262500 281 ns/op 56 B/op 3 allocs/op +BenchmarkLARS_ParamWrite 10764410 113 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_ParamWrite 486769 2726 ns/op 1176 B/op 14 allocs/op +BenchmarkMartini_ParamWrite 264804 4842 ns/op 1176 B/op 14 allocs/op +BenchmarkPat_ParamWrite 735116 2047 ns/op 960 B/op 15 allocs/op +BenchmarkPossum_ParamWrite 1000000 1004 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_ParamWrite 1592136 768 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_ParamWrite 3582051 339 ns/op 112 B/op 2 allocs/op +BenchmarkTango_ParamWrite 2237337 534 ns/op 136 B/op 4 allocs/op +BenchmarkTigerTonic_ParamWrite 439608 3136 ns/op 1216 B/op 21 allocs/op +BenchmarkTraffic_ParamWrite 306979 4328 ns/op 2280 B/op 25 allocs/op +BenchmarkVulcan_ParamWrite 2529973 472 ns/op 98 B/op 3 allocs/op ``` ## GitHub -``` -BenchmarkGin_GithubStatic 5866748 194 ns/op 0 B/op 0 allocs/op - -BenchmarkAce_GithubStatic 5815826 205 ns/op 0 B/op 0 allocs/op -BenchmarkAero_GithubStatic 10822906 106 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GithubStatic 1678065 707 ns/op 120 B/op 3 allocs/op -BenchmarkBeego_GithubStatic 828814 1717 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GithubStatic 67484 18858 ns/op 2880 B/op 60 allocs/op -BenchmarkCloudyKitRouter_GithubStatic 10219297 115 ns/op 0 B/op 0 allocs/op -BenchmarkChi_GithubStatic 1000000 1348 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_GithubStatic 15220622 75.4 ns/op 0 B/op 0 allocs/op -BenchmarkEcho_GithubStatic 7255897 158 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GithubStatic 1000000 1198 ns/op 296 B/op 5 allocs/op -BenchmarkGoji_GithubStatic 3659361 320 ns/op 0 B/op 0 allocs/op -BenchmarkGojiv2_GithubStatic 402402 3384 ns/op 1312 B/op 10 allocs/op -BenchmarkGoRestful_GithubStatic 54592 22045 ns/op 4256 B/op 13 allocs/op -BenchmarkGoJsonRest_GithubStatic 801067 1673 ns/op 329 B/op 11 allocs/op -BenchmarkGorillaMux_GithubStatic 169690 8171 ns/op 976 B/op 9 allocs/op -BenchmarkGowwwRouter_GithubStatic 5372910 218 ns/op 0 B/op 0 allocs/op -BenchmarkHttpRouter_GithubStatic 10965576 103 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_GithubStatic 10505365 106 ns/op 0 B/op 0 allocs/op -BenchmarkKocha_GithubStatic 14153763 81.9 ns/op 0 B/op 0 allocs/op -BenchmarkLARS_GithubStatic 7874017 152 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GithubStatic 696940 2678 ns/op 736 B/op 8 allocs/op -BenchmarkMartini_GithubStatic 102384 12276 ns/op 768 B/op 9 allocs/op -BenchmarkPat_GithubStatic 69907 17437 ns/op 3648 B/op 76 allocs/op -BenchmarkPossum_GithubStatic 1000000 1262 ns/op 416 B/op 3 allocs/op -BenchmarkR2router_GithubStatic 1981592 614 ns/op 144 B/op 4 allocs/op -BenchmarkRivet_GithubStatic 6103872 196 ns/op 0 B/op 0 allocs/op -BenchmarkTango_GithubStatic 629551 2023 ns/op 248 B/op 8 allocs/op -BenchmarkTigerTonic_GithubStatic 2801569 424 ns/op 48 B/op 1 allocs/op -BenchmarkTraffic_GithubStatic 63716 18009 ns/op 4664 B/op 90 allocs/op -BenchmarkVulcan_GithubStatic 885640 1177 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_GithubParam 2016942 582 ns/op 96 B/op 1 allocs/op -BenchmarkAero_GithubParam 4009522 296 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GithubParam 1000000 1575 ns/op 496 B/op 5 allocs/op -BenchmarkBeego_GithubParam 796662 2038 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GithubParam 114823 10325 ns/op 1888 B/op 19 allocs/op -BenchmarkChi_GithubParam 1000000 1783 ns/op 432 B/op 3 allocs/op -BenchmarkCloudyKitRouter_GithubParam 3910996 303 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_GithubParam 2298172 521 ns/op 128 B/op 1 allocs/op -BenchmarkEcho_GithubParam 3336364 357 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GithubParam 2729161 439 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GithubParam 825784 2338 ns/op 712 B/op 9 allocs/op -BenchmarkGoji_GithubParam 933397 1559 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_GithubParam 253884 4335 ns/op 1408 B/op 13 allocs/op -BenchmarkGoJsonRest_GithubParam 575532 2967 ns/op 713 B/op 14 allocs/op -BenchmarkGoRestful_GithubParam 38160 30638 ns/op 4352 B/op 16 allocs/op -BenchmarkGorillaMux_GithubParam 94554 12035 ns/op 1296 B/op 10 allocs/op -BenchmarkGowwwRouter_GithubParam 1000000 1223 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_GithubParam 2562079 468 ns/op 96 B/op 1 allocs/op -BenchmarkHttpTreeMux_GithubParam 1000000 1386 ns/op 384 B/op 4 allocs/op -BenchmarkKocha_GithubParam 1573026 754 ns/op 128 B/op 5 allocs/op -BenchmarkLARS_GithubParam 4203394 282 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GithubParam 365078 4137 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_GithubParam 71608 15811 ns/op 1152 B/op 11 allocs/op -BenchmarkPat_GithubParam 92768 13297 ns/op 2408 B/op 48 allocs/op -BenchmarkPossum_GithubParam 1000000 1704 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_GithubParam 1000000 1120 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_GithubParam 1642794 720 ns/op 96 B/op 1 allocs/op -BenchmarkTango_GithubParam 574195 2345 ns/op 344 B/op 8 allocs/op -BenchmarkTigerTonic_GithubParam 272430 5493 ns/op 1176 B/op 22 allocs/op -BenchmarkTraffic_GithubParam 81914 15078 ns/op 2816 B/op 40 allocs/op -BenchmarkVulcan_GithubParam 581272 1902 ns/op 98 B/op 3 allocs/op - - -BenchmarkAce_GithubAll 10000 103571 ns/op 13792 B/op 167 allocs/op -BenchmarkAero_GithubAll 21366 55615 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GithubAll 5288 327648 ns/op 86448 B/op 943 allocs/op -BenchmarkBeego_GithubAll 3974 413453 ns/op 71456 B/op 609 allocs/op -BenchmarkBone_GithubAll 267 4450294 ns/op 720160 B/op 8620 allocs/op -BenchmarkChi_GithubAll 5067 358773 ns/op 87696 B/op 609 allocs/op -BenchmarkCloudyKitRouter_GithubAll 24210 49233 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_GithubAll 12508 95341 ns/op 20224 B/op 167 allocs/op -BenchmarkEcho_GithubAll 16353 73267 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GithubAll 15516 77716 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GithubAll 2908 466970 ns/op 131656 B/op 1686 allocs/op -BenchmarkGoji_GithubAll 1746 691392 ns/op 56112 B/op 334 allocs/op -BenchmarkGojiv2_GithubAll 954 1289604 ns/op 352720 B/op 4321 allocs/op -BenchmarkGoJsonRest_GithubAll 2013 599088 ns/op 134371 B/op 2737 allocs/op -BenchmarkGoRestful_GithubAll 223 5404307 ns/op 910144 B/op 2938 allocs/op -BenchmarkGorillaMux_GithubAll 202 5943565 ns/op 251650 B/op 1994 allocs/op -BenchmarkGowwwRouter_GithubAll 9009 227799 ns/op 72144 B/op 501 allocs/op -BenchmarkHttpRouter_GithubAll 14570 78718 ns/op 13792 B/op 167 allocs/op -BenchmarkHttpTreeMux_GithubAll 7226 242491 ns/op 65856 B/op 671 allocs/op -BenchmarkKocha_GithubAll 8282 159873 ns/op 23304 B/op 843 allocs/op -BenchmarkLARS_GithubAll 22711 52745 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GithubAll 2067 563117 ns/op 149409 B/op 1624 allocs/op -BenchmarkMartini_GithubAll 218 5455290 ns/op 226552 B/op 2325 allocs/op -BenchmarkPat_GithubAll 174 6801582 ns/op 1483152 B/op 26963 allocs/op -BenchmarkPossum_GithubAll 8113 263665 ns/op 84448 B/op 609 allocs/op -BenchmarkR2router_GithubAll 7172 247198 ns/op 77328 B/op 979 allocs/op -BenchmarkRivet_GithubAll 10000 128086 ns/op 16272 B/op 167 allocs/op -BenchmarkTango_GithubAll 3316 472753 ns/op 63825 B/op 1618 allocs/op -BenchmarkTigerTonic_GithubAll 1176 1041991 ns/op 193856 B/op 4474 allocs/op -BenchmarkTraffic_GithubAll 226 5312082 ns/op 820742 B/op 14114 allocs/op -BenchmarkVulcan_GithubAll 3904 304440 ns/op 19894 B/op 609 allocs/op +```sh +BenchmarkGin_GithubStatic 15629472 76.7 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_GithubStatic 15542612 75.9 ns/op 0 B/op 0 allocs/op +BenchmarkAero_GithubStatic 24777151 48.5 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GithubStatic 2788894 435 ns/op 120 B/op 3 allocs/op +BenchmarkBeego_GithubStatic 1000000 1064 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GithubStatic 93507 12838 ns/op 2880 B/op 60 allocs/op +BenchmarkChi_GithubStatic 1387743 860 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_GithubStatic 39384996 30.4 ns/op 0 B/op 0 allocs/op +BenchmarkEcho_GithubStatic 12076382 99.1 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GithubStatic 1596495 756 ns/op 296 B/op 5 allocs/op +BenchmarkGoji_GithubStatic 6364876 189 ns/op 0 B/op 0 allocs/op +BenchmarkGojiv2_GithubStatic 550202 2098 ns/op 1312 B/op 10 allocs/op +BenchmarkGoRestful_GithubStatic 102183 12552 ns/op 4256 B/op 13 allocs/op +BenchmarkGoJsonRest_GithubStatic 1000000 1029 ns/op 329 B/op 11 allocs/op +BenchmarkGorillaMux_GithubStatic 255552 5190 ns/op 976 B/op 9 allocs/op +BenchmarkGowwwRouter_GithubStatic 15531916 77.1 ns/op 0 B/op 0 allocs/op +BenchmarkHttpRouter_GithubStatic 27920724 43.1 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_GithubStatic 21448953 55.8 ns/op 0 B/op 0 allocs/op +BenchmarkKocha_GithubStatic 21405310 56.0 ns/op 0 B/op 0 allocs/op +BenchmarkLARS_GithubStatic 13625156 89.0 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GithubStatic 1000000 1747 ns/op 736 B/op 8 allocs/op +BenchmarkMartini_GithubStatic 187186 7326 ns/op 768 B/op 9 allocs/op +BenchmarkPat_GithubStatic 109143 11563 ns/op 3648 B/op 76 allocs/op +BenchmarkPossum_GithubStatic 1575898 770 ns/op 416 B/op 3 allocs/op +BenchmarkR2router_GithubStatic 3046231 404 ns/op 144 B/op 4 allocs/op +BenchmarkRivet_GithubStatic 11484826 105 ns/op 0 B/op 0 allocs/op +BenchmarkTango_GithubStatic 1000000 1153 ns/op 248 B/op 8 allocs/op +BenchmarkTigerTonic_GithubStatic 4929780 249 ns/op 48 B/op 1 allocs/op +BenchmarkTraffic_GithubStatic 106351 11819 ns/op 4664 B/op 90 allocs/op +BenchmarkVulcan_GithubStatic 1613271 722 ns/op 98 B/op 3 allocs/op +BenchmarkAce_GithubParam 8386032 143 ns/op 0 B/op 0 allocs/op +BenchmarkAero_GithubParam 11816200 102 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GithubParam 1000000 1012 ns/op 496 B/op 5 allocs/op +BenchmarkBeego_GithubParam 1000000 1157 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GithubParam 184653 6912 ns/op 1888 B/op 19 allocs/op +BenchmarkChi_GithubParam 1000000 1102 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_GithubParam 3484798 352 ns/op 128 B/op 1 allocs/op +BenchmarkEcho_GithubParam 6337380 189 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GithubParam 9132032 131 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GithubParam 1000000 1446 ns/op 712 B/op 9 allocs/op +BenchmarkGoji_GithubParam 1248640 977 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_GithubParam 383233 2784 ns/op 1408 B/op 13 allocs/op +BenchmarkGoJsonRest_GithubParam 1000000 1991 ns/op 713 B/op 14 allocs/op +BenchmarkGoRestful_GithubParam 76414 16015 ns/op 4352 B/op 16 allocs/op +BenchmarkGorillaMux_GithubParam 150026 7663 ns/op 1296 B/op 10 allocs/op +BenchmarkGowwwRouter_GithubParam 1592044 751 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_GithubParam 10420628 115 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_GithubParam 1403755 835 ns/op 384 B/op 4 allocs/op +BenchmarkKocha_GithubParam 2286170 533 ns/op 128 B/op 5 allocs/op +BenchmarkLARS_GithubParam 9540374 129 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GithubParam 533154 2742 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_GithubParam 119397 9638 ns/op 1152 B/op 11 allocs/op +BenchmarkPat_GithubParam 150675 8858 ns/op 2408 B/op 48 allocs/op +BenchmarkPossum_GithubParam 1000000 1001 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_GithubParam 1602886 761 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_GithubParam 2986579 409 ns/op 96 B/op 1 allocs/op +BenchmarkTango_GithubParam 1000000 1356 ns/op 344 B/op 8 allocs/op +BenchmarkTigerTonic_GithubParam 388899 3429 ns/op 1176 B/op 22 allocs/op +BenchmarkTraffic_GithubParam 123160 9734 ns/op 2816 B/op 40 allocs/op +BenchmarkVulcan_GithubParam 1000000 1138 ns/op 98 B/op 3 allocs/op +BenchmarkAce_GithubAll 40543 29670 ns/op 0 B/op 0 allocs/op +BenchmarkAero_GithubAll 57632 20648 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GithubAll 9234 216179 ns/op 86448 B/op 943 allocs/op +BenchmarkBeego_GithubAll 7407 243496 ns/op 71456 B/op 609 allocs/op +BenchmarkBone_GithubAll 420 2922835 ns/op 720160 B/op 8620 allocs/op +BenchmarkChi_GithubAll 7620 238331 ns/op 87696 B/op 609 allocs/op +BenchmarkDenco_GithubAll 18355 64494 ns/op 20224 B/op 167 allocs/op +BenchmarkEcho_GithubAll 31251 38479 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GithubAll 43550 27364 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GithubAll 4117 300062 ns/op 131656 B/op 1686 allocs/op +BenchmarkGoji_GithubAll 3274 416158 ns/op 56112 B/op 334 allocs/op +BenchmarkGojiv2_GithubAll 1402 870518 ns/op 352720 B/op 4321 allocs/op +BenchmarkGoJsonRest_GithubAll 2976 401507 ns/op 134371 B/op 2737 allocs/op +BenchmarkGoRestful_GithubAll 410 2913158 ns/op 910144 B/op 2938 allocs/op +BenchmarkGorillaMux_GithubAll 346 3384987 ns/op 251650 B/op 1994 allocs/op +BenchmarkGowwwRouter_GithubAll 10000 143025 ns/op 72144 B/op 501 allocs/op +BenchmarkHttpRouter_GithubAll 55938 21360 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_GithubAll 10000 153944 ns/op 65856 B/op 671 allocs/op +BenchmarkKocha_GithubAll 10000 106315 ns/op 23304 B/op 843 allocs/op +BenchmarkLARS_GithubAll 47779 25084 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GithubAll 3266 371907 ns/op 149409 B/op 1624 allocs/op +BenchmarkMartini_GithubAll 331 3444706 ns/op 226551 B/op 2325 allocs/op +BenchmarkPat_GithubAll 273 4381818 ns/op 1483152 B/op 26963 allocs/op +BenchmarkPossum_GithubAll 10000 164367 ns/op 84448 B/op 609 allocs/op +BenchmarkR2router_GithubAll 10000 160220 ns/op 77328 B/op 979 allocs/op +BenchmarkRivet_GithubAll 14625 82453 ns/op 16272 B/op 167 allocs/op +BenchmarkTango_GithubAll 6255 279611 ns/op 63826 B/op 1618 allocs/op +BenchmarkTigerTonic_GithubAll 2008 687874 ns/op 193856 B/op 4474 allocs/op +BenchmarkTraffic_GithubAll 355 3478508 ns/op 820744 B/op 14114 allocs/op +BenchmarkVulcan_GithubAll 6885 193333 ns/op 19894 B/op 609 allocs/op ``` ## Google+ -``` -BenchmarkGin_GPlusStatic 9172405 124 ns/op 0 B/op 0 allocs/op - -BenchmarkAce_GPlusStatic 7784710 152 ns/op 0 B/op 0 allocs/op -BenchmarkAero_GPlusStatic 12771894 89.2 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GPlusStatic 2351325 512 ns/op 104 B/op 3 allocs/op -BenchmarkBeego_GPlusStatic 1000000 1643 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GPlusStatic 4419217 263 ns/op 32 B/op 1 allocs/op -BenchmarkChi_GPlusStatic 1000000 1282 ns/op 432 B/op 3 allocs/op -BenchmarkCloudyKitRouter_GPlusStatic 17730754 61.9 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_GPlusStatic 29549895 38.3 ns/op 0 B/op 0 allocs/op -BenchmarkEcho_GPlusStatic 10521789 111 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GPlusStatic 1000000 1053 ns/op 280 B/op 5 allocs/op -BenchmarkGoji_GPlusStatic 5209968 228 ns/op 0 B/op 0 allocs/op -BenchmarkGojiv2_GPlusStatic 306363 3348 ns/op 1312 B/op 10 allocs/op -BenchmarkGoJsonRest_GPlusStatic 1000000 1424 ns/op 329 B/op 11 allocs/op -BenchmarkGoRestful_GPlusStatic 130754 8760 ns/op 3872 B/op 13 allocs/op -BenchmarkGorillaMux_GPlusStatic 496250 2860 ns/op 976 B/op 9 allocs/op -BenchmarkGowwwRouter_GPlusStatic 16401519 66.5 ns/op 0 B/op 0 allocs/op -BenchmarkHttpRouter_GPlusStatic 21323139 50.3 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_GPlusStatic 14877926 68.7 ns/op 0 B/op 0 allocs/op -BenchmarkKocha_GPlusStatic 18375128 57.6 ns/op 0 B/op 0 allocs/op -BenchmarkLARS_GPlusStatic 11153810 101 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GPlusStatic 652598 2720 ns/op 736 B/op 8 allocs/op -BenchmarkMartini_GPlusStatic 218824 6532 ns/op 768 B/op 9 allocs/op -BenchmarkPat_GPlusStatic 2825560 428 ns/op 96 B/op 2 allocs/op -BenchmarkPossum_GPlusStatic 1000000 1236 ns/op 416 B/op 3 allocs/op -BenchmarkR2router_GPlusStatic 2222193 541 ns/op 144 B/op 4 allocs/op -BenchmarkRivet_GPlusStatic 9802023 114 ns/op 0 B/op 0 allocs/op -BenchmarkTango_GPlusStatic 980658 1465 ns/op 200 B/op 8 allocs/op -BenchmarkTigerTonic_GPlusStatic 4882701 239 ns/op 32 B/op 1 allocs/op -BenchmarkTraffic_GPlusStatic 508060 3465 ns/op 1112 B/op 16 allocs/op -BenchmarkVulcan_GPlusStatic 1608979 725 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_GPlusParam 2962957 414 ns/op 64 B/op 1 allocs/op -BenchmarkAero_GPlusParam 5667668 202 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GPlusParam 1000000 1271 ns/op 480 B/op 5 allocs/op -BenchmarkBeego_GPlusParam 869858 1874 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GPlusParam 869476 2395 ns/op 816 B/op 6 allocs/op -BenchmarkChi_GPlusParam 1000000 1469 ns/op 432 B/op 3 allocs/op -BenchmarkCloudyKitRouter_GPlusParam 11149783 108 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_GPlusParam 4007298 301 ns/op 64 B/op 1 allocs/op -BenchmarkEcho_GPlusParam 6448201 174 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GPlusParam 5470827 218 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GPlusParam 1000000 1939 ns/op 648 B/op 8 allocs/op -BenchmarkGoji_GPlusParam 1207621 997 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_GPlusParam 271326 4013 ns/op 1328 B/op 11 allocs/op -BenchmarkGoJsonRest_GPlusParam 781062 2303 ns/op 649 B/op 13 allocs/op -BenchmarkGoRestful_GPlusParam 121267 9871 ns/op 4192 B/op 14 allocs/op -BenchmarkGorillaMux_GPlusParam 228406 5156 ns/op 1280 B/op 10 allocs/op -BenchmarkGowwwRouter_GPlusParam 1000000 1074 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_GPlusParam 4399740 276 ns/op 64 B/op 1 allocs/op -BenchmarkHttpTreeMux_GPlusParam 1309540 898 ns/op 352 B/op 3 allocs/op -BenchmarkKocha_GPlusParam 2930965 403 ns/op 56 B/op 3 allocs/op -BenchmarkLARS_GPlusParam 7588237 151 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GPlusParam 434997 4195 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_GPlusParam 148207 8144 ns/op 1072 B/op 10 allocs/op -BenchmarkPat_GPlusParam 566829 2533 ns/op 576 B/op 11 allocs/op -BenchmarkPossum_GPlusParam 1000000 1723 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_GPlusParam 1000000 1100 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_GPlusParam 3309052 331 ns/op 48 B/op 1 allocs/op -BenchmarkTango_GPlusParam 693728 1825 ns/op 264 B/op 8 allocs/op -BenchmarkTigerTonic_GPlusParam 417693 3800 ns/op 856 B/op 16 allocs/op -BenchmarkTraffic_GPlusParam 179424 6641 ns/op 1872 B/op 21 allocs/op -BenchmarkVulcan_GPlusParam 1000000 1063 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_GPlus2Params 2720149 460 ns/op 64 B/op 1 allocs/op -BenchmarkAero_GPlus2Params 3525165 343 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GPlus2Params 1000000 1502 ns/op 496 B/op 5 allocs/op -BenchmarkBeego_GPlus2Params 730123 2102 ns/op 352 B/op 3 allocs/op -BenchmarkBone_GPlus2Params 253177 5583 ns/op 1168 B/op 10 allocs/op -BenchmarkChi_GPlus2Params 1000000 1531 ns/op 432 B/op 3 allocs/op -BenchmarkCloudyKitRouter_GPlus2Params 6943176 168 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_GPlus2Params 2912601 413 ns/op 64 B/op 1 allocs/op -BenchmarkEcho_GPlus2Params 4149189 278 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GPlus2Params 3271269 356 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GPlus2Params 915531 2321 ns/op 712 B/op 9 allocs/op -BenchmarkGoji_GPlus2Params 1000000 1413 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_GPlus2Params 256640 4521 ns/op 1408 B/op 14 allocs/op -BenchmarkGoJsonRest_GPlus2Params 499140 3076 ns/op 713 B/op 14 allocs/op -BenchmarkGoRestful_GPlus2Params 105928 10148 ns/op 4384 B/op 16 allocs/op -BenchmarkGorillaMux_GPlus2Params 110953 9682 ns/op 1296 B/op 10 allocs/op -BenchmarkGowwwRouter_GPlus2Params 1000000 1112 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_GPlus2Params 3491893 321 ns/op 64 B/op 1 allocs/op -BenchmarkHttpTreeMux_GPlus2Params 1000000 1341 ns/op 384 B/op 4 allocs/op -BenchmarkKocha_GPlus2Params 1445288 790 ns/op 128 B/op 5 allocs/op -BenchmarkLARS_GPlus2Params 6644953 185 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GPlus2Params 424291 4321 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_GPlus2Params 70866 16407 ns/op 1200 B/op 13 allocs/op -BenchmarkPat_GPlus2Params 121308 10221 ns/op 2168 B/op 33 allocs/op -BenchmarkPossum_GPlus2Params 1000000 1847 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_GPlus2Params 1000000 1267 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_GPlus2Params 2017526 590 ns/op 96 B/op 1 allocs/op -BenchmarkTango_GPlus2Params 846003 2143 ns/op 344 B/op 8 allocs/op -BenchmarkTigerTonic_GPlus2Params 303597 5736 ns/op 1200 B/op 22 allocs/op -BenchmarkTraffic_GPlus2Params 95032 12817 ns/op 2248 B/op 28 allocs/op -BenchmarkVulcan_GPlus2Params 692610 1575 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_GPlusAll 271720 4948 ns/op 640 B/op 11 allocs/op -BenchmarkAero_GPlusAll 367956 2926 ns/op 0 B/op 0 allocs/op -BenchmarkBear_GPlusAll 68161 17883 ns/op 5488 B/op 61 allocs/op -BenchmarkBeego_GPlusAll 46634 25369 ns/op 4576 B/op 39 allocs/op -BenchmarkBone_GPlusAll 24628 49198 ns/op 11744 B/op 109 allocs/op -BenchmarkChi_GPlusAll 60778 19356 ns/op 5616 B/op 39 allocs/op -BenchmarkCloudyKitRouter_GPlusAll 706952 1693 ns/op 0 B/op 0 allocs/op -BenchmarkDenco_GPlusAll 327422 4222 ns/op 672 B/op 11 allocs/op -BenchmarkEcho_GPlusAll 331987 3176 ns/op 0 B/op 0 allocs/op -BenchmarkGin_GPlusAll 289526 3559 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_GPlusAll 45805 26768 ns/op 8040 B/op 103 allocs/op -BenchmarkGoji_GPlusAll 74786 14428 ns/op 3696 B/op 22 allocs/op -BenchmarkGojiv2_GPlusAll 23822 50355 ns/op 17616 B/op 154 allocs/op -BenchmarkGoJsonRest_GPlusAll 35280 32989 ns/op 8117 B/op 170 allocs/op -BenchmarkGoRestful_GPlusAll 10000 129418 ns/op 55520 B/op 192 allocs/op -BenchmarkGorillaMux_GPlusAll 15968 76492 ns/op 16112 B/op 128 allocs/op -BenchmarkGowwwRouter_GPlusAll 100096 12644 ns/op 4752 B/op 33 allocs/op -BenchmarkHttpRouter_GPlusAll 474584 3704 ns/op 640 B/op 11 allocs/op -BenchmarkHttpTreeMux_GPlusAll 98506 12480 ns/op 4032 B/op 38 allocs/op -BenchmarkKocha_GPlusAll 213709 7358 ns/op 976 B/op 43 allocs/op -BenchmarkLARS_GPlusAll 466608 2363 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_GPlusAll 34136 35790 ns/op 9568 B/op 104 allocs/op -BenchmarkMartini_GPlusAll 8911 124543 ns/op 14016 B/op 145 allocs/op -BenchmarkPat_GPlusAll 17391 69198 ns/op 15264 B/op 271 allocs/op -BenchmarkPossum_GPlusAll 66774 17004 ns/op 5408 B/op 39 allocs/op -BenchmarkR2router_GPlusAll 79681 13996 ns/op 5040 B/op 63 allocs/op -BenchmarkRivet_GPlusAll 258788 5344 ns/op 768 B/op 11 allocs/op -BenchmarkTango_GPlusAll 46930 25591 ns/op 3656 B/op 104 allocs/op -BenchmarkTigerTonic_GPlusAll 20768 58038 ns/op 11600 B/op 242 allocs/op -BenchmarkTraffic_GPlusAll 10000 108031 ns/op 26248 B/op 341 allocs/op -BenchmarkVulcan_GPlusAll 71826 15724 ns/op 1274 B/op 39 allocs/op +```sh +BenchmarkGin_GPlusStatic 19247326 62.2 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_GPlusStatic 20235060 59.2 ns/op 0 B/op 0 allocs/op +BenchmarkAero_GPlusStatic 31978935 37.6 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GPlusStatic 3516523 341 ns/op 104 B/op 3 allocs/op +BenchmarkBeego_GPlusStatic 1212036 991 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GPlusStatic 6736242 183 ns/op 32 B/op 1 allocs/op +BenchmarkChi_GPlusStatic 1490640 814 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_GPlusStatic 55006856 21.8 ns/op 0 B/op 0 allocs/op +BenchmarkEcho_GPlusStatic 17688258 67.9 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GPlusStatic 1829181 666 ns/op 280 B/op 5 allocs/op +BenchmarkGoji_GPlusStatic 9147451 130 ns/op 0 B/op 0 allocs/op +BenchmarkGojiv2_GPlusStatic 594015 2063 ns/op 1312 B/op 10 allocs/op +BenchmarkGoJsonRest_GPlusStatic 1264906 950 ns/op 329 B/op 11 allocs/op +BenchmarkGoRestful_GPlusStatic 231558 5341 ns/op 3872 B/op 13 allocs/op +BenchmarkGorillaMux_GPlusStatic 908418 1809 ns/op 976 B/op 9 allocs/op +BenchmarkGowwwRouter_GPlusStatic 40684604 29.5 ns/op 0 B/op 0 allocs/op +BenchmarkHttpRouter_GPlusStatic 46742804 25.7 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_GPlusStatic 32567161 36.9 ns/op 0 B/op 0 allocs/op +BenchmarkKocha_GPlusStatic 33800060 35.3 ns/op 0 B/op 0 allocs/op +BenchmarkLARS_GPlusStatic 20431858 60.0 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GPlusStatic 1000000 1745 ns/op 736 B/op 8 allocs/op +BenchmarkMartini_GPlusStatic 442248 3619 ns/op 768 B/op 9 allocs/op +BenchmarkPat_GPlusStatic 4328004 292 ns/op 96 B/op 2 allocs/op +BenchmarkPossum_GPlusStatic 1570753 763 ns/op 416 B/op 3 allocs/op +BenchmarkR2router_GPlusStatic 3339474 355 ns/op 144 B/op 4 allocs/op +BenchmarkRivet_GPlusStatic 18570961 64.7 ns/op 0 B/op 0 allocs/op +BenchmarkTango_GPlusStatic 1388702 860 ns/op 200 B/op 8 allocs/op +BenchmarkTigerTonic_GPlusStatic 7803543 159 ns/op 32 B/op 1 allocs/op +BenchmarkTraffic_GPlusStatic 878605 2171 ns/op 1112 B/op 16 allocs/op +BenchmarkVulcan_GPlusStatic 2742446 437 ns/op 98 B/op 3 allocs/op +BenchmarkAce_GPlusParam 11626975 105 ns/op 0 B/op 0 allocs/op +BenchmarkAero_GPlusParam 16914322 71.6 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GPlusParam 1405173 832 ns/op 480 B/op 5 allocs/op +BenchmarkBeego_GPlusParam 1000000 1075 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GPlusParam 1000000 1557 ns/op 816 B/op 6 allocs/op +BenchmarkChi_GPlusParam 1347926 894 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_GPlusParam 5513000 212 ns/op 64 B/op 1 allocs/op +BenchmarkEcho_GPlusParam 11884383 101 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GPlusParam 12898952 93.1 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GPlusParam 1000000 1194 ns/op 648 B/op 8 allocs/op +BenchmarkGoji_GPlusParam 1857229 645 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_GPlusParam 520939 2322 ns/op 1328 B/op 11 allocs/op +BenchmarkGoJsonRest_GPlusParam 1000000 1536 ns/op 649 B/op 13 allocs/op +BenchmarkGoRestful_GPlusParam 205449 5800 ns/op 4192 B/op 14 allocs/op +BenchmarkGorillaMux_GPlusParam 395310 3188 ns/op 1280 B/op 10 allocs/op +BenchmarkGowwwRouter_GPlusParam 1851798 667 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_GPlusParam 18420789 65.2 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_GPlusParam 1878463 629 ns/op 352 B/op 3 allocs/op +BenchmarkKocha_GPlusParam 4495610 273 ns/op 56 B/op 3 allocs/op +BenchmarkLARS_GPlusParam 14615976 83.2 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GPlusParam 584145 2549 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_GPlusParam 250501 4583 ns/op 1072 B/op 10 allocs/op +BenchmarkPat_GPlusParam 1000000 1645 ns/op 576 B/op 11 allocs/op +BenchmarkPossum_GPlusParam 1000000 1008 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_GPlusParam 1708191 688 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_GPlusParam 5795014 211 ns/op 48 B/op 1 allocs/op +BenchmarkTango_GPlusParam 1000000 1091 ns/op 264 B/op 8 allocs/op +BenchmarkTigerTonic_GPlusParam 760221 2489 ns/op 856 B/op 16 allocs/op +BenchmarkTraffic_GPlusParam 309774 4039 ns/op 1872 B/op 21 allocs/op +BenchmarkVulcan_GPlusParam 1935730 623 ns/op 98 B/op 3 allocs/op +BenchmarkAce_GPlus2Params 9158314 134 ns/op 0 B/op 0 allocs/op +BenchmarkAero_GPlus2Params 11300517 107 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GPlus2Params 1239238 961 ns/op 496 B/op 5 allocs/op +BenchmarkBeego_GPlus2Params 1000000 1202 ns/op 352 B/op 3 allocs/op +BenchmarkBone_GPlus2Params 335576 3725 ns/op 1168 B/op 10 allocs/op +BenchmarkChi_GPlus2Params 1000000 1014 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_GPlus2Params 4394598 280 ns/op 64 B/op 1 allocs/op +BenchmarkEcho_GPlus2Params 7851861 154 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GPlus2Params 9958588 120 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GPlus2Params 1000000 1433 ns/op 712 B/op 9 allocs/op +BenchmarkGoji_GPlus2Params 1325134 909 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_GPlus2Params 405955 2870 ns/op 1408 B/op 14 allocs/op +BenchmarkGoJsonRest_GPlus2Params 977038 1987 ns/op 713 B/op 14 allocs/op +BenchmarkGoRestful_GPlus2Params 205018 6142 ns/op 4384 B/op 16 allocs/op +BenchmarkGorillaMux_GPlus2Params 205641 6015 ns/op 1296 B/op 10 allocs/op +BenchmarkGowwwRouter_GPlus2Params 1748542 684 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_GPlus2Params 14047102 87.7 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_GPlus2Params 1418673 828 ns/op 384 B/op 4 allocs/op +BenchmarkKocha_GPlus2Params 2334562 520 ns/op 128 B/op 5 allocs/op +BenchmarkLARS_GPlus2Params 11954094 101 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GPlus2Params 491552 2890 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_GPlus2Params 120532 9545 ns/op 1200 B/op 13 allocs/op +BenchmarkPat_GPlus2Params 194739 6766 ns/op 2168 B/op 33 allocs/op +BenchmarkPossum_GPlus2Params 1201224 1009 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_GPlus2Params 1575535 756 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_GPlus2Params 3698930 325 ns/op 96 B/op 1 allocs/op +BenchmarkTango_GPlus2Params 1000000 1212 ns/op 344 B/op 8 allocs/op +BenchmarkTigerTonic_GPlus2Params 349350 3660 ns/op 1200 B/op 22 allocs/op +BenchmarkTraffic_GPlus2Params 169714 7862 ns/op 2248 B/op 28 allocs/op +BenchmarkVulcan_GPlus2Params 1222288 974 ns/op 98 B/op 3 allocs/op +BenchmarkAce_GPlusAll 845606 1398 ns/op 0 B/op 0 allocs/op +BenchmarkAero_GPlusAll 1000000 1009 ns/op 0 B/op 0 allocs/op +BenchmarkBear_GPlusAll 103830 11386 ns/op 5488 B/op 61 allocs/op +BenchmarkBeego_GPlusAll 82653 14784 ns/op 4576 B/op 39 allocs/op +BenchmarkBone_GPlusAll 36601 33123 ns/op 11744 B/op 109 allocs/op +BenchmarkChi_GPlusAll 95264 12831 ns/op 5616 B/op 39 allocs/op +BenchmarkDenco_GPlusAll 567681 2950 ns/op 672 B/op 11 allocs/op +BenchmarkEcho_GPlusAll 720366 1665 ns/op 0 B/op 0 allocs/op +BenchmarkGin_GPlusAll 1000000 1185 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_GPlusAll 71575 16365 ns/op 8040 B/op 103 allocs/op +BenchmarkGoji_GPlusAll 136352 9191 ns/op 3696 B/op 22 allocs/op +BenchmarkGojiv2_GPlusAll 38006 31802 ns/op 17616 B/op 154 allocs/op +BenchmarkGoJsonRest_GPlusAll 57238 21561 ns/op 8117 B/op 170 allocs/op +BenchmarkGoRestful_GPlusAll 15147 79276 ns/op 55520 B/op 192 allocs/op +BenchmarkGorillaMux_GPlusAll 24446 48410 ns/op 16112 B/op 128 allocs/op +BenchmarkGowwwRouter_GPlusAll 150112 7770 ns/op 4752 B/op 33 allocs/op +BenchmarkHttpRouter_GPlusAll 1367820 878 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_GPlusAll 166628 8004 ns/op 4032 B/op 38 allocs/op +BenchmarkKocha_GPlusAll 265694 4570 ns/op 976 B/op 43 allocs/op +BenchmarkLARS_GPlusAll 1000000 1068 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_GPlusAll 54564 23305 ns/op 9568 B/op 104 allocs/op +BenchmarkMartini_GPlusAll 16274 73845 ns/op 14016 B/op 145 allocs/op +BenchmarkPat_GPlusAll 27181 44478 ns/op 15264 B/op 271 allocs/op +BenchmarkPossum_GPlusAll 122587 10277 ns/op 5408 B/op 39 allocs/op +BenchmarkR2router_GPlusAll 130137 9297 ns/op 5040 B/op 63 allocs/op +BenchmarkRivet_GPlusAll 532438 3323 ns/op 768 B/op 11 allocs/op +BenchmarkTango_GPlusAll 86054 14531 ns/op 3656 B/op 104 allocs/op +BenchmarkTigerTonic_GPlusAll 33936 35356 ns/op 11600 B/op 242 allocs/op +BenchmarkTraffic_GPlusAll 17833 68181 ns/op 26248 B/op 341 allocs/op +BenchmarkVulcan_GPlusAll 120109 9861 ns/op 1274 B/op 39 allocs/op ``` ## Parse.com -``` -BenchmarkGin_ParseStatic 8683893 140 ns/op 0 B/op 0 allocs/op - -BenchmarkAce_ParseStatic 7255582 160 ns/op 0 B/op 0 allocs/op -BenchmarkAero_ParseStatic 11960128 95.0 ns/op 0 B/op 0 allocs/op -BenchmarkBear_ParseStatic 1791033 659 ns/op 120 B/op 3 allocs/op -BenchmarkBeego_ParseStatic 937918 1688 ns/op 352 B/op 3 allocs/op -BenchmarkBone_ParseStatic 1261682 949 ns/op 144 B/op 3 allocs/op -BenchmarkChi_ParseStatic 1000000 1303 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_ParseStatic 23731242 49.8 ns/op 0 B/op 0 allocs/op -BenchmarkEcho_ParseStatic 10585060 116 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_ParseStatic 1000000 1156 ns/op 296 B/op 5 allocs/op -BenchmarkGoji_ParseStatic 3927530 300 ns/op 0 B/op 0 allocs/op -BenchmarkGojiv2_ParseStatic 474836 3281 ns/op 1312 B/op 10 allocs/op -BenchmarkGoJsonRest_ParseStatic 1000000 1445 ns/op 329 B/op 11 allocs/op -BenchmarkGoRestful_ParseStatic 101262 11612 ns/op 4256 B/op 13 allocs/op -BenchmarkGorillaMux_ParseStatic 562705 3530 ns/op 976 B/op 9 allocs/op -BenchmarkGowwwRouter_ParseStatic 16479007 69.5 ns/op 0 B/op 0 allocs/op -BenchmarkHttpRouter_ParseStatic 23205590 51.5 ns/op 0 B/op 0 allocs/op -BenchmarkHttpTreeMux_ParseStatic 10763127 106 ns/op 0 B/op 0 allocs/op -BenchmarkKocha_ParseStatic 17850259 60.9 ns/op 0 B/op 0 allocs/op -BenchmarkLARS_ParseStatic 10727432 108 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_ParseStatic 685586 2665 ns/op 736 B/op 8 allocs/op -BenchmarkMartini_ParseStatic 200642 7158 ns/op 768 B/op 9 allocs/op -BenchmarkPat_ParseStatic 1000000 1139 ns/op 240 B/op 5 allocs/op -BenchmarkPossum_ParseStatic 1000000 1241 ns/op 416 B/op 3 allocs/op -BenchmarkR2router_ParseStatic 2035426 597 ns/op 144 B/op 4 allocs/op -BenchmarkRivet_ParseStatic 9707011 127 ns/op 0 B/op 0 allocs/op -BenchmarkTango_ParseStatic 910617 1693 ns/op 248 B/op 8 allocs/op -BenchmarkTigerTonic_ParseStatic 3168885 385 ns/op 48 B/op 1 allocs/op -BenchmarkTraffic_ParseStatic 493339 4264 ns/op 1256 B/op 19 allocs/op -BenchmarkVulcan_ParseStatic 1394142 848 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_ParseParam 3106903 387 ns/op 64 B/op 1 allocs/op -BenchmarkAero_ParseParam 8045266 141 ns/op 0 B/op 0 allocs/op -BenchmarkBear_ParseParam 1000000 1434 ns/op 467 B/op 5 allocs/op -BenchmarkBeego_ParseParam 951460 1937 ns/op 352 B/op 3 allocs/op -BenchmarkBone_ParseParam 855555 2776 ns/op 896 B/op 7 allocs/op -BenchmarkChi_ParseParam 1000000 1457 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_ParseParam 4084116 301 ns/op 64 B/op 1 allocs/op -BenchmarkEcho_ParseParam 8440170 142 ns/op 0 B/op 0 allocs/op -BenchmarkGin_ParseParam 7716948 157 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_ParseParam 886284 2045 ns/op 664 B/op 8 allocs/op -BenchmarkGoji_ParseParam 1000000 1167 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_ParseParam 269731 3945 ns/op 1360 B/op 12 allocs/op -BenchmarkGoJsonRest_ParseParam 719587 2277 ns/op 649 B/op 13 allocs/op -BenchmarkGoRestful_ParseParam 96408 11925 ns/op 4576 B/op 14 allocs/op -BenchmarkGorillaMux_ParseParam 289303 4154 ns/op 1280 B/op 10 allocs/op -BenchmarkGowwwRouter_ParseParam 1000000 1070 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_ParseParam 4917758 232 ns/op 64 B/op 1 allocs/op -BenchmarkHttpTreeMux_ParseParam 1445443 828 ns/op 352 B/op 3 allocs/op -BenchmarkKocha_ParseParam 3116233 382 ns/op 56 B/op 3 allocs/op -BenchmarkLARS_ParseParam 10584750 113 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_ParseParam 413617 3872 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_ParseParam 166545 7605 ns/op 1072 B/op 10 allocs/op -BenchmarkPat_ParseParam 491829 3394 ns/op 992 B/op 15 allocs/op -BenchmarkPossum_ParseParam 1000000 1692 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_ParseParam 1000000 1059 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_ParseParam 3572359 311 ns/op 48 B/op 1 allocs/op -BenchmarkTango_ParseParam 787552 1889 ns/op 280 B/op 8 allocs/op -BenchmarkTigerTonic_ParseParam 487208 3706 ns/op 784 B/op 15 allocs/op -BenchmarkTraffic_ParseParam 186190 5812 ns/op 1896 B/op 21 allocs/op -BenchmarkVulcan_ParseParam 1275432 892 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_Parse2Params 2959621 412 ns/op 64 B/op 1 allocs/op -BenchmarkAero_Parse2Params 6208641 192 ns/op 0 B/op 0 allocs/op -BenchmarkBear_Parse2Params 1000000 1512 ns/op 496 B/op 5 allocs/op -BenchmarkBeego_Parse2Params 761940 1973 ns/op 352 B/op 3 allocs/op -BenchmarkBone_Parse2Params 715987 2582 ns/op 848 B/op 6 allocs/op -BenchmarkChi_Parse2Params 1000000 1495 ns/op 432 B/op 3 allocs/op -BenchmarkDenco_Parse2Params 3585452 341 ns/op 64 B/op 1 allocs/op -BenchmarkEcho_Parse2Params 5193693 204 ns/op 0 B/op 0 allocs/op -BenchmarkGin_Parse2Params 5338316 236 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_Parse2Params 939637 2299 ns/op 712 B/op 9 allocs/op -BenchmarkGoji_Parse2Params 1000000 1094 ns/op 336 B/op 2 allocs/op -BenchmarkGojiv2_Parse2Params 339514 3733 ns/op 1344 B/op 11 allocs/op -BenchmarkGoJsonRest_Parse2Params 512572 2733 ns/op 713 B/op 14 allocs/op -BenchmarkGoRestful_Parse2Params 95913 12973 ns/op 4928 B/op 14 allocs/op -BenchmarkGorillaMux_Parse2Params 261208 4758 ns/op 1296 B/op 10 allocs/op -BenchmarkGowwwRouter_Parse2Params 1000000 1084 ns/op 432 B/op 3 allocs/op -BenchmarkHttpRouter_Parse2Params 4399953 277 ns/op 64 B/op 1 allocs/op -BenchmarkHttpTreeMux_Parse2Params 1000000 1198 ns/op 384 B/op 4 allocs/op -BenchmarkKocha_Parse2Params 1669431 683 ns/op 128 B/op 5 allocs/op -BenchmarkLARS_Parse2Params 8535754 142 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_Parse2Params 424590 3959 ns/op 1072 B/op 10 allocs/op -BenchmarkMartini_Parse2Params 162448 8141 ns/op 1152 B/op 11 allocs/op -BenchmarkPat_Parse2Params 431336 3484 ns/op 752 B/op 16 allocs/op -BenchmarkPossum_Parse2Params 1000000 1721 ns/op 496 B/op 5 allocs/op -BenchmarkR2router_Parse2Params 1000000 1136 ns/op 432 B/op 5 allocs/op -BenchmarkRivet_Parse2Params 2630935 442 ns/op 96 B/op 1 allocs/op -BenchmarkTango_Parse2Params 759218 1876 ns/op 312 B/op 8 allocs/op -BenchmarkTigerTonic_Parse2Params 290810 5558 ns/op 1168 B/op 22 allocs/op -BenchmarkTraffic_Parse2Params 181099 6917 ns/op 1944 B/op 22 allocs/op -BenchmarkVulcan_Parse2Params 1000000 1080 ns/op 98 B/op 3 allocs/op - -BenchmarkAce_ParseAll 162906 7888 ns/op 640 B/op 16 allocs/op -BenchmarkAero_ParseAll 219260 4833 ns/op 0 B/op 0 allocs/op -BenchmarkBear_ParseAll 37566 32863 ns/op 8928 B/op 110 allocs/op -BenchmarkBeego_ParseAll 25400 46518 ns/op 9152 B/op 78 allocs/op -BenchmarkBone_ParseAll 19568 61814 ns/op 16208 B/op 147 allocs/op -BenchmarkChi_ParseAll 30562 38281 ns/op 11232 B/op 78 allocs/op -BenchmarkDenco_ParseAll 232554 6371 ns/op 928 B/op 16 allocs/op -BenchmarkEcho_ParseAll 224400 5090 ns/op 0 B/op 0 allocs/op -BenchmarkGin_ParseAll 189829 6134 ns/op 0 B/op 0 allocs/op -BenchmarkGocraftWeb_ParseAll 25446 47000 ns/op 13728 B/op 181 allocs/op -BenchmarkGoji_ParseAll 50503 22949 ns/op 5376 B/op 32 allocs/op -BenchmarkGojiv2_ParseAll 12806 93106 ns/op 34448 B/op 277 allocs/op -BenchmarkGoJsonRest_ParseAll 20764 57021 ns/op 13866 B/op 321 allocs/op -BenchmarkGoRestful_ParseAll 4234 317238 ns/op 117600 B/op 354 allocs/op -BenchmarkGorillaMux_ParseAll 10000 146942 ns/op 30288 B/op 250 allocs/op -BenchmarkGowwwRouter_ParseAll 62548 19363 ns/op 6912 B/op 48 allocs/op -BenchmarkHttpRouter_ParseAll 286662 5091 ns/op 640 B/op 16 allocs/op -BenchmarkHttpTreeMux_ParseAll 66952 18262 ns/op 5728 B/op 51 allocs/op -BenchmarkKocha_ParseAll 109771 9811 ns/op 1112 B/op 54 allocs/op -BenchmarkLARS_ParseAll 272516 3976 ns/op 0 B/op 0 allocs/op -BenchmarkMacaron_ParseAll 17094 71634 ns/op 19136 B/op 208 allocs/op -BenchmarkMartini_ParseAll 6799 208122 ns/op 25072 B/op 253 allocs/op -BenchmarkPat_ParseAll 15993 74594 ns/op 15216 B/op 308 allocs/op -BenchmarkPossum_ParseAll 34897 33398 ns/op 10816 B/op 78 allocs/op -BenchmarkR2router_ParseAll 46909 25410 ns/op 8352 B/op 120 allocs/op -BenchmarkRivet_ParseAll 185193 7725 ns/op 912 B/op 16 allocs/op -BenchmarkTango_ParseAll 24481 47963 ns/op 7168 B/op 208 allocs/op -BenchmarkTigerTonic_ParseAll 15236 79623 ns/op 16048 B/op 332 allocs/op -BenchmarkTraffic_ParseAll 8955 169411 ns/op 45520 B/op 605 allocs/op -BenchmarkVulcan_ParseAll 40406 28971 ns/op 2548 B/op 78 allocs/op +```sh +BenchmarkGin_ParseStatic 18877833 63.5 ns/op 0 B/op 0 allocs/op + +BenchmarkAce_ParseStatic 19663731 60.8 ns/op 0 B/op 0 allocs/op +BenchmarkAero_ParseStatic 28967341 41.5 ns/op 0 B/op 0 allocs/op +BenchmarkBear_ParseStatic 3006984 402 ns/op 120 B/op 3 allocs/op +BenchmarkBeego_ParseStatic 1000000 1031 ns/op 352 B/op 3 allocs/op +BenchmarkBone_ParseStatic 1782482 675 ns/op 144 B/op 3 allocs/op +BenchmarkChi_ParseStatic 1453261 819 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_ParseStatic 45023595 26.5 ns/op 0 B/op 0 allocs/op +BenchmarkEcho_ParseStatic 17330470 69.3 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_ParseStatic 1644006 731 ns/op 296 B/op 5 allocs/op +BenchmarkGoji_ParseStatic 7026930 170 ns/op 0 B/op 0 allocs/op +BenchmarkGojiv2_ParseStatic 517618 2037 ns/op 1312 B/op 10 allocs/op +BenchmarkGoJsonRest_ParseStatic 1227080 975 ns/op 329 B/op 11 allocs/op +BenchmarkGoRestful_ParseStatic 192458 6659 ns/op 4256 B/op 13 allocs/op +BenchmarkGorillaMux_ParseStatic 744062 2109 ns/op 976 B/op 9 allocs/op +BenchmarkGowwwRouter_ParseStatic 37781062 31.8 ns/op 0 B/op 0 allocs/op +BenchmarkHttpRouter_ParseStatic 45311223 26.5 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_ParseStatic 21383475 56.1 ns/op 0 B/op 0 allocs/op +BenchmarkKocha_ParseStatic 29953290 40.1 ns/op 0 B/op 0 allocs/op +BenchmarkLARS_ParseStatic 20036196 62.7 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_ParseStatic 1000000 1740 ns/op 736 B/op 8 allocs/op +BenchmarkMartini_ParseStatic 404156 3801 ns/op 768 B/op 9 allocs/op +BenchmarkPat_ParseStatic 1547180 772 ns/op 240 B/op 5 allocs/op +BenchmarkPossum_ParseStatic 1608991 757 ns/op 416 B/op 3 allocs/op +BenchmarkR2router_ParseStatic 3177936 385 ns/op 144 B/op 4 allocs/op +BenchmarkRivet_ParseStatic 17783205 67.4 ns/op 0 B/op 0 allocs/op +BenchmarkTango_ParseStatic 1210777 990 ns/op 248 B/op 8 allocs/op +BenchmarkTigerTonic_ParseStatic 5316440 231 ns/op 48 B/op 1 allocs/op +BenchmarkTraffic_ParseStatic 496050 2539 ns/op 1256 B/op 19 allocs/op +BenchmarkVulcan_ParseStatic 2462798 488 ns/op 98 B/op 3 allocs/op +BenchmarkAce_ParseParam 13393669 89.6 ns/op 0 B/op 0 allocs/op +BenchmarkAero_ParseParam 19836619 60.4 ns/op 0 B/op 0 allocs/op +BenchmarkBear_ParseParam 1405954 864 ns/op 467 B/op 5 allocs/op +BenchmarkBeego_ParseParam 1000000 1065 ns/op 352 B/op 3 allocs/op +BenchmarkBone_ParseParam 1000000 1698 ns/op 896 B/op 7 allocs/op +BenchmarkChi_ParseParam 1356037 873 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_ParseParam 6241392 204 ns/op 64 B/op 1 allocs/op +BenchmarkEcho_ParseParam 14088100 85.1 ns/op 0 B/op 0 allocs/op +BenchmarkGin_ParseParam 17426064 68.9 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_ParseParam 1000000 1254 ns/op 664 B/op 8 allocs/op +BenchmarkGoji_ParseParam 1682574 713 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_ParseParam 502224 2333 ns/op 1360 B/op 12 allocs/op +BenchmarkGoJsonRest_ParseParam 1000000 1401 ns/op 649 B/op 13 allocs/op +BenchmarkGoRestful_ParseParam 182623 7097 ns/op 4576 B/op 14 allocs/op +BenchmarkGorillaMux_ParseParam 482332 2477 ns/op 1280 B/op 10 allocs/op +BenchmarkGowwwRouter_ParseParam 1834873 657 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_ParseParam 23593393 51.0 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_ParseParam 2100160 574 ns/op 352 B/op 3 allocs/op +BenchmarkKocha_ParseParam 4837220 252 ns/op 56 B/op 3 allocs/op +BenchmarkLARS_ParseParam 18411192 66.2 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_ParseParam 571870 2398 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_ParseParam 286262 4268 ns/op 1072 B/op 10 allocs/op +BenchmarkPat_ParseParam 692906 2157 ns/op 992 B/op 15 allocs/op +BenchmarkPossum_ParseParam 1000000 1011 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_ParseParam 1722735 697 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_ParseParam 6058054 203 ns/op 48 B/op 1 allocs/op +BenchmarkTango_ParseParam 1000000 1061 ns/op 280 B/op 8 allocs/op +BenchmarkTigerTonic_ParseParam 890275 2277 ns/op 784 B/op 15 allocs/op +BenchmarkTraffic_ParseParam 351322 3543 ns/op 1896 B/op 21 allocs/op +BenchmarkVulcan_ParseParam 2076544 572 ns/op 98 B/op 3 allocs/op +BenchmarkAce_Parse2Params 11718074 101 ns/op 0 B/op 0 allocs/op +BenchmarkAero_Parse2Params 16264988 73.4 ns/op 0 B/op 0 allocs/op +BenchmarkBear_Parse2Params 1238322 973 ns/op 496 B/op 5 allocs/op +BenchmarkBeego_Parse2Params 1000000 1120 ns/op 352 B/op 3 allocs/op +BenchmarkBone_Parse2Params 1000000 1632 ns/op 848 B/op 6 allocs/op +BenchmarkChi_Parse2Params 1239477 955 ns/op 432 B/op 3 allocs/op +BenchmarkDenco_Parse2Params 4944133 245 ns/op 64 B/op 1 allocs/op +BenchmarkEcho_Parse2Params 10518286 114 ns/op 0 B/op 0 allocs/op +BenchmarkGin_Parse2Params 14505195 82.7 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_Parse2Params 1000000 1437 ns/op 712 B/op 9 allocs/op +BenchmarkGoji_Parse2Params 1689883 707 ns/op 336 B/op 2 allocs/op +BenchmarkGojiv2_Parse2Params 502334 2308 ns/op 1344 B/op 11 allocs/op +BenchmarkGoJsonRest_Parse2Params 1000000 1771 ns/op 713 B/op 14 allocs/op +BenchmarkGoRestful_Parse2Params 159092 7583 ns/op 4928 B/op 14 allocs/op +BenchmarkGorillaMux_Parse2Params 417548 2980 ns/op 1296 B/op 10 allocs/op +BenchmarkGowwwRouter_Parse2Params 1751737 686 ns/op 432 B/op 3 allocs/op +BenchmarkHttpRouter_Parse2Params 18089204 66.3 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_Parse2Params 1556986 777 ns/op 384 B/op 4 allocs/op +BenchmarkKocha_Parse2Params 2493082 485 ns/op 128 B/op 5 allocs/op +BenchmarkLARS_Parse2Params 15350108 78.5 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_Parse2Params 530974 2605 ns/op 1072 B/op 10 allocs/op +BenchmarkMartini_Parse2Params 247069 4673 ns/op 1152 B/op 11 allocs/op +BenchmarkPat_Parse2Params 816295 2126 ns/op 752 B/op 16 allocs/op +BenchmarkPossum_Parse2Params 1000000 1002 ns/op 496 B/op 5 allocs/op +BenchmarkR2router_Parse2Params 1569771 733 ns/op 432 B/op 5 allocs/op +BenchmarkRivet_Parse2Params 4080546 295 ns/op 96 B/op 1 allocs/op +BenchmarkTango_Parse2Params 1000000 1121 ns/op 312 B/op 8 allocs/op +BenchmarkTigerTonic_Parse2Params 399556 3470 ns/op 1168 B/op 22 allocs/op +BenchmarkTraffic_Parse2Params 314194 4159 ns/op 1944 B/op 22 allocs/op +BenchmarkVulcan_Parse2Params 1827559 664 ns/op 98 B/op 3 allocs/op +BenchmarkAce_ParseAll 478395 2503 ns/op 0 B/op 0 allocs/op +BenchmarkAero_ParseAll 715392 1658 ns/op 0 B/op 0 allocs/op +BenchmarkBear_ParseAll 59191 20124 ns/op 8928 B/op 110 allocs/op +BenchmarkBeego_ParseAll 45507 27266 ns/op 9152 B/op 78 allocs/op +BenchmarkBone_ParseAll 29328 41459 ns/op 16208 B/op 147 allocs/op +BenchmarkChi_ParseAll 48531 25053 ns/op 11232 B/op 78 allocs/op +BenchmarkDenco_ParseAll 325532 4284 ns/op 928 B/op 16 allocs/op +BenchmarkEcho_ParseAll 433771 2759 ns/op 0 B/op 0 allocs/op +BenchmarkGin_ParseAll 576316 2082 ns/op 0 B/op 0 allocs/op +BenchmarkGocraftWeb_ParseAll 41500 29692 ns/op 13728 B/op 181 allocs/op +BenchmarkGoji_ParseAll 80833 15563 ns/op 5376 B/op 32 allocs/op +BenchmarkGojiv2_ParseAll 19836 60335 ns/op 34448 B/op 277 allocs/op +BenchmarkGoJsonRest_ParseAll 32210 38027 ns/op 13866 B/op 321 allocs/op +BenchmarkGoRestful_ParseAll 6644 190842 ns/op 117600 B/op 354 allocs/op +BenchmarkGorillaMux_ParseAll 12634 95894 ns/op 30288 B/op 250 allocs/op +BenchmarkGowwwRouter_ParseAll 98152 12159 ns/op 6912 B/op 48 allocs/op +BenchmarkHttpRouter_ParseAll 933208 1273 ns/op 0 B/op 0 allocs/op +BenchmarkHttpTreeMux_ParseAll 107191 11554 ns/op 5728 B/op 51 allocs/op +BenchmarkKocha_ParseAll 184862 6225 ns/op 1112 B/op 54 allocs/op +BenchmarkLARS_ParseAll 644546 1858 ns/op 0 B/op 0 allocs/op +BenchmarkMacaron_ParseAll 26145 46484 ns/op 19136 B/op 208 allocs/op +BenchmarkMartini_ParseAll 10000 121838 ns/op 25072 B/op 253 allocs/op +BenchmarkPat_ParseAll 25417 47196 ns/op 15216 B/op 308 allocs/op +BenchmarkPossum_ParseAll 58550 20735 ns/op 10816 B/op 78 allocs/op +BenchmarkR2router_ParseAll 72732 16584 ns/op 8352 B/op 120 allocs/op +BenchmarkRivet_ParseAll 281365 4968 ns/op 912 B/op 16 allocs/op +BenchmarkTango_ParseAll 42831 28668 ns/op 7168 B/op 208 allocs/op +BenchmarkTigerTonic_ParseAll 23774 49972 ns/op 16048 B/op 332 allocs/op +BenchmarkTraffic_ParseAll 10000 104679 ns/op 45520 B/op 605 allocs/op +BenchmarkVulcan_ParseAll 64810 18108 ns/op 2548 B/op 78 allocs/op ``` diff --git a/README.md b/README.md index dae63e2..d75843a 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [AsciiJSON](#asciijson) - [PureJSON](#purejson) - [Serving static files](#serving-static-files) + - [Serving data from file](#serving-data-from-file) - [Serving data from reader](#serving-data-from-reader) - [HTML rendering](#html-rendering) - [Custom Template renderer](#custom-template-renderer) @@ -68,6 +69,8 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Support Let's Encrypt](#support-lets-encrypt) - [Run multiple service using Gin](#run-multiple-service-using-gin) - [Graceful shutdown or restart](#graceful-shutdown-or-restart) + - [Third-party packages](#third-party-packages) + - [Manually](#manually) - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) @@ -133,35 +136,38 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr [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) | +| ------------------------------ | ---------:| ---------------:| ------------:| ---------------:| +| BenchmarkGin_GithubAll | **43550** | **27364 ns/op** | **0 B/op** | **0 allocs/op** | +| BenchmarkAce_GithubAll | 40543 | 29670 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkAero_GithubAll | 57632 | 20648 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkBear_GithubAll | 9234 | 216179 ns/op | 86448 B/op | 943 allocs/op | +| BenchmarkBeego_GithubAll | 7407 | 243496 ns/op | 71456 B/op | 609 allocs/op | +| BenchmarkBone_GithubAll | 420 | 2922835 ns/op | 720160 B/op | 8620 allocs/op | +| BenchmarkChi_GithubAll | 7620 | 238331 ns/op | 87696 B/op | 609 allocs/op | +| BenchmarkDenco_GithubAll | 18355 | 64494 ns/op | 20224 B/op | 167 allocs/op | +| BenchmarkEcho_GithubAll | 31251 | 38479 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkGocraftWeb_GithubAll | 4117 | 300062 ns/op | 131656 B/op | 1686 allocs/op | +| BenchmarkGoji_GithubAll | 3274 | 416158 ns/op | 56112 B/op | 334 allocs/op | +| BenchmarkGojiv2_GithubAll | 1402 | 870518 ns/op | 352720 B/op | 4321 allocs/op | +| BenchmarkGoJsonRest_GithubAll | 2976 | 401507 ns/op | 134371 B/op | 2737 allocs/op | +| BenchmarkGoRestful_GithubAll | 410 | 2913158 ns/op | 910144 B/op | 2938 allocs/op | +| BenchmarkGorillaMux_GithubAll | 346 | 3384987 ns/op | 251650 B/op | 1994 allocs/op | +| BenchmarkGowwwRouter_GithubAll | 10000 | 143025 ns/op | 72144 B/op | 501 allocs/op | +| BenchmarkHttpRouter_GithubAll | 55938 | 21360 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkHttpTreeMux_GithubAll | 10000 | 153944 ns/op | 65856 B/op | 671 allocs/op | +| BenchmarkKocha_GithubAll | 10000 | 106315 ns/op | 23304 B/op | 843 allocs/op | +| BenchmarkLARS_GithubAll | 47779 | 25084 ns/op | 0 B/op | 0 allocs/op | +| BenchmarkMacaron_GithubAll | 3266 | 371907 ns/op | 149409 B/op | 1624 allocs/op | +| BenchmarkMartini_GithubAll | 331 | 3444706 ns/op | 226551 B/op | 2325 allocs/op | +| BenchmarkPat_GithubAll | 273 | 4381818 ns/op | 1483152 B/op | 26963 allocs/op | +| BenchmarkPossum_GithubAll | 10000 | 164367 ns/op | 84448 B/op | 609 allocs/op | +| BenchmarkR2router_GithubAll | 10000 | 160220 ns/op | 77328 B/op | 979 allocs/op | +| BenchmarkRivet_GithubAll | 14625 | 82453 ns/op | 16272 B/op | 167 allocs/op | +| BenchmarkTango_GithubAll | 6255 | 279611 ns/op | 63826 B/op | 1618 allocs/op | +| BenchmarkTigerTonic_GithubAll | 2008 | 687874 ns/op | 193856 B/op | 4474 allocs/op | +| BenchmarkTraffic_GithubAll | 355 | 3478508 ns/op | 820744 B/op | 14114 allocs/op | +| BenchmarkVulcan_GithubAll | 6885 | 193333 ns/op | 19894 B/op | 609 allocs/op | - (1): Total Repetitions achieved in constant time, higher means more confident result - (2): Single Repetition Duration (ns/op), lower is better From 235898e64285eb5c2cfdd485f21220e4deab3006 Mon Sep 17 00:00:00 2001 From: bn4t <17193640+bn4t@users.noreply.github.com> Date: Tue, 5 May 2020 15:20:00 +0000 Subject: [PATCH 477/912] Fix typo (#2358) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d75843a..5c145d5 100644 --- a/README.md +++ b/README.md @@ -1763,7 +1763,7 @@ func main() { // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit - log.Println("Shuting down server...") + log.Println("Shutting down server...") // The context is used to inform the server it has 5 seconds to finish // the request it is currently handling From 82b284fd774d6bf68c934a3f335b816c61edf66f Mon Sep 17 00:00:00 2001 From: Anup Kumar Panwar <1anuppanwar@gmail.com> Date: Fri, 8 May 2020 06:20:26 +0530 Subject: [PATCH 478/912] uncomment the code (#2362) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5c145d5..9cc23fc 100644 --- a/README.md +++ b/README.md @@ -357,14 +357,14 @@ References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail func main() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) - // router.MaxMultipartMemory = 8 << 20 // 8 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)) }) @@ -388,7 +388,7 @@ See the detail [example code](https://github.com/gin-gonic/examples/tree/master/ func main() { router := gin.Default() // Set a lower memory limit for multipart forms (default is 32 MiB) - // router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // Multipart form form, _ := c.MultipartForm() @@ -398,7 +398,7 @@ func main() { 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))) }) From 6f3d96ccff56290fd506dff68af21a56a22d0873 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 9 May 2020 17:41:00 +0800 Subject: [PATCH 479/912] chore: improve render string performance (#2365) --- render/text.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/render/text.go b/render/text.go index 4e52d4c..30f5f53 100644 --- a/render/text.go +++ b/render/text.go @@ -6,7 +6,6 @@ package render import ( "fmt" - "io" "net/http" ) @@ -35,6 +34,6 @@ func WriteString(w http.ResponseWriter, format string, data []interface{}) (err _, err = fmt.Fprintf(w, format, data...) return } - _, err = io.WriteString(w, format) + _, err = w.Write([]byte(format)) return } From d17270dd90c488308de3102d6951946ca0a5911f Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 10 May 2020 13:22:25 +0800 Subject: [PATCH 480/912] Sync route tree to httprouter latest code (#2368) * update tree Signed-off-by: Bo-Yi Wu * update Signed-off-by: Bo-Yi Wu * update Signed-off-by: Bo-Yi Wu * update countParams Signed-off-by: Bo-Yi Wu * fix testing Signed-off-by: Bo-Yi Wu * update Signed-off-by: Bo-Yi Wu * update Signed-off-by: Bo-Yi Wu * udpate Signed-off-by: Bo-Yi Wu * fix testing Signed-off-by: Bo-Yi Wu * refactor gin context Signed-off-by: Bo-Yi Wu * add fullPath Signed-off-by: Bo-Yi Wu * chore: refactor * remove unused code Signed-off-by: Bo-Yi Wu * remove varsCount Signed-off-by: Bo-Yi Wu * refactor Signed-off-by: Bo-Yi Wu --- context.go | 2 + gin.go | 16 +- tree.go | 573 +++++++++++++++++++++++++++++---------------------- tree_test.go | 84 ++++---- 4 files changed, 393 insertions(+), 282 deletions(-) diff --git a/context.go b/context.go index 4ebcc29..2ea1a02 100644 --- a/context.go +++ b/context.go @@ -54,6 +54,7 @@ type Context struct { fullPath string engine *Engine + params *Params // This mutex protect Keys map mu sync.RWMutex @@ -95,6 +96,7 @@ func (c *Context) reset() { c.Accepted = nil c.queryCache = nil c.formCache = nil + *c.params = (*c.params)[0:0] } // Copy returns a copy of the current context that can be safely used outside the request's scope. diff --git a/gin.go b/gin.go index 4e72f81..1e12617 100644 --- a/gin.go +++ b/gin.go @@ -113,6 +113,7 @@ type Engine struct { noMethod HandlersChain pool sync.Pool trees methodTrees + maxParams uint16 } var _ IRouter = &Engine{} @@ -163,7 +164,8 @@ func Default() *Engine { } func (engine *Engine) allocateContext() *Context { - return &Context{engine: engine} + v := make(Params, 0, engine.maxParams) + return &Context{engine: engine, params: &v} } // Delims sets template left and right delims and returns a Engine instance. @@ -256,6 +258,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { assert1(len(handlers) > 0, "there must be at least one handler") debugPrintRoute(method, path, handlers) + root := engine.trees.get(method) if root == nil { root = new(node) @@ -263,6 +266,11 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) + + // Update maxParams + if paramsCount := countParams(path); paramsCount > engine.maxParams { + engine.maxParams = paramsCount + } } // Routes returns a slice of registered routes, including some useful information, such as: @@ -402,10 +410,12 @@ func (engine *Engine) handleHTTPRequest(c *Context) { } root := t[i].root // Find route in tree - value := root.getValue(rPath, c.Params, unescape) + value := root.getValue(rPath, c.params, unescape) + if value.params != nil { + c.Params = *value.params + } if value.handlers != nil { c.handlers = value.handlers - c.Params = value.params c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() diff --git a/tree.go b/tree.go index b687ec4..e3aa919 100644 --- a/tree.go +++ b/tree.go @@ -8,6 +8,7 @@ import ( "net/url" "strings" "unicode" + "unicode/utf8" ) // Param is a single URL parameter, consisting of a key and a value. @@ -71,17 +72,15 @@ func longestCommonPrefix(a, b string) int { return i } -func countParams(path string) uint8 { +func countParams(path string) uint16 { var n uint - for i := 0; i < len(path); i++ { - if path[i] == ':' || path[i] == '*' { + for i := range []byte(path) { + switch path[i] { + case ':', '*': n++ } } - if n >= 255 { - return 255 - } - return uint8(n) + return uint16(n) } type nodeType uint8 @@ -96,16 +95,15 @@ const ( type node struct { path string indices string + wildChild bool + nType nodeType + priority uint32 children []*node handlers HandlersChain - priority uint32 - nType nodeType - maxParams uint8 - wildChild bool fullPath string } -// increments priority of the given child and reorders if necessary. +// Increments priority of the given child and reorders if necessary func (n *node) incrementChildPrio(pos int) int { cs := n.children cs[pos].priority++ @@ -116,13 +114,14 @@ func (n *node) incrementChildPrio(pos int) int { for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- { // Swap node positions cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1] + } - // build new index char string + // Build new index char string if newPos != pos { - n.indices = n.indices[:newPos] + // unchanged prefix, might be empty - n.indices[pos:pos+1] + // the index char we move - n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos' + n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty + n.indices[pos:pos+1] + // The index char we move + n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos' } return newPos @@ -133,11 +132,10 @@ func (n *node) incrementChildPrio(pos int) int { func (n *node) addRoute(path string, handlers HandlersChain) { fullPath := path n.priority++ - numParams := countParams(path) // Empty tree if len(n.path) == 0 && len(n.children) == 0 { - n.insertChild(numParams, path, fullPath, handlers) + n.insertChild(path, fullPath, handlers) n.nType = root return } @@ -146,11 +144,6 @@ func (n *node) addRoute(path string, handlers HandlersChain) { walk: for { - // Update maxParams of the current node - if numParams > n.maxParams { - n.maxParams = numParams - } - // Find the longest common prefix. // This also implies that the common prefix contains no ':' or '*' // since the existing key can't contain those chars. @@ -168,13 +161,6 @@ walk: fullPath: n.fullPath, } - // Update maxParams (max of all children) - for _, v := range child.children { - if v.maxParams > child.maxParams { - child.maxParams = v.maxParams - } - } - n.children = []*node{&child} // []byte for proper unicode char conversion, see #65 n.indices = string([]byte{n.path[i]}) @@ -193,18 +179,13 @@ walk: n = n.children[0] n.priority++ - // Update maxParams of the child node - if numParams > n.maxParams { - n.maxParams = numParams - } - numParams-- - // Check if the wildcard matches - if len(path) >= len(n.path) && n.path == path[:len(n.path)] { - // check for longer wildcard, e.g. :name and :names - if len(n.path) >= len(path) || path[len(n.path)] == '/' { - continue walk - } + if len(path) >= len(n.path) && n.path == path[:len(n.path)] && + // Adding a child to a catchAll is not possible + n.nType != catchAll && + // Check for longer wildcard, e.g. :name and :names + (len(n.path) >= len(path) || path[len(n.path)] == '/') { + continue walk } pathSeg := path @@ -244,14 +225,13 @@ walk: // []byte for proper unicode char conversion, see #65 n.indices += string([]byte{c}) child := &node{ - maxParams: numParams, - fullPath: fullPath, + fullPath: fullPath, } n.children = append(n.children, child) n.incrementChildPrio(len(n.indices) - 1) n = child } - n.insertChild(numParams, path, fullPath, handlers) + n.insertChild(path, fullPath, handlers) return } @@ -265,7 +245,7 @@ walk: } // Search for a wildcard segment and check the name for invalid characters. -// Returns -1 as index, if no wildcard war found. +// Returns -1 as index, if no wildcard was found. func findWildcard(path string) (wildcard string, i int, valid bool) { // Find start for start, c := range []byte(path) { @@ -289,8 +269,8 @@ func findWildcard(path string) (wildcard string, i int, valid bool) { return "", -1, false } -func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { - for numParams > 0 { +func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) { + for { // Find prefix until first wildcard wildcard, i, valid := findWildcard(path) if i < 0 { // No wildcard found @@ -324,15 +304,13 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle n.wildChild = true child := &node{ - nType: param, - path: wildcard, - maxParams: numParams, - fullPath: fullPath, + nType: param, + path: wildcard, + fullPath: fullPath, } n.children = []*node{child} n = child n.priority++ - numParams-- // if the path doesn't end with the wildcard, then there // will be another non-wildcard subpath starting with '/' @@ -340,9 +318,8 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle path = path[len(wildcard):] child := &node{ - maxParams: numParams, - priority: 1, - fullPath: fullPath, + priority: 1, + fullPath: fullPath, } n.children = []*node{child} n = child @@ -355,7 +332,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle } // catchAll - if i+len(wildcard) != len(path) || numParams > 1 { + if i+len(wildcard) != len(path) { panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'") } @@ -375,13 +352,9 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle child := &node{ wildChild: true, nType: catchAll, - maxParams: 1, fullPath: fullPath, } - // update maxParams of the parent node - if n.maxParams < 1 { - n.maxParams = 1 - } + n.children = []*node{child} n.indices = string('/') n = child @@ -389,12 +362,11 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle // second node: node holding the variable child = &node{ - path: path[i:], - nType: catchAll, - maxParams: 1, - handlers: handlers, - priority: 1, - fullPath: fullPath, + path: path[i:], + nType: catchAll, + handlers: handlers, + priority: 1, + fullPath: fullPath, } n.children = []*node{child} @@ -410,21 +382,128 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle // nodeValue holds return values of (*Node).getValue method type nodeValue struct { handlers HandlersChain - params Params + params *Params tsr bool fullPath string } -// getValue returns the handle registered with the given path (key). The values of +// Returns the handle registered with the given path (key). The values of // wildcards are saved to a map. // If no handle can be found, a TSR (trailing slash redirect) recommendation is // made if a handle exists with an extra (without the) trailing slash for the // given path. -func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) { - value.params = po +func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) { walk: // Outer loop for walking the tree for { prefix := n.path + if len(path) > len(prefix) { + if path[:len(prefix)] == prefix { + path = path[len(prefix):] + // If this node does not have a wildcard (param or catchAll) + // child, we can just look up the next child node and continue + // to walk down the tree + if !n.wildChild { + idxc := path[0] + for i, c := range []byte(n.indices) { + if c == idxc { + n = n.children[i] + continue walk + } + } + + // Nothing found. + // We can recommend to redirect to the same URL without a + // trailing slash if a leaf exists for that path. + value.tsr = (path == "/" && n.handlers != nil) + return + } + + // Handle wildcard child + n = n.children[0] + switch n.nType { + case param: + // Find param end (either '/' or path end) + end := 0 + for end < len(path) && path[end] != '/' { + end++ + } + + // Save param value + if params != nil { + if value.params == nil { + value.params = params + } + // Expand slice within preallocated capacity + i := len(*value.params) + *value.params = (*value.params)[:i+1] + val := path[:end] + if unescape { + if v, err := url.QueryUnescape(val); err == nil { + val = v + } + } + (*value.params)[i] = Param{ + Key: n.path[1:], + Value: val, + } + } + + // we need to go deeper! + if end < len(path) { + if len(n.children) > 0 { + path = path[end:] + n = n.children[0] + continue walk + } + + // ... but we can't + value.tsr = (len(path) == end+1) + return + } + + if value.handlers = n.handlers; value.handlers != nil { + value.fullPath = n.fullPath + return + } + if len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists for TSR recommendation + n = n.children[0] + value.tsr = (n.path == "/" && n.handlers != nil) + } + return + + case catchAll: + // Save param value + if params != nil { + if value.params == nil { + value.params = params + } + // Expand slice within preallocated capacity + i := len(*value.params) + *value.params = (*value.params)[:i+1] + val := path + if unescape { + if v, err := url.QueryUnescape(path); err == nil { + val = v + } + } + (*value.params)[i] = Param{ + Key: n.path[2:], + Value: val, + } + } + + value.handlers = n.handlers + value.fullPath = n.fullPath + return + + default: + panic("invalid node type") + } + } + } + if path == prefix { // We should have reached the node containing the handle. // Check if this node has a handle registered. @@ -433,6 +512,9 @@ walk: // Outer loop for walking the tree return } + // If there is no handle for this route, but this route has a + // wildcard child, there must be a handle for this path with an + // additional trailing slash if path == "/" && n.wildChild && n.nType != root { value.tsr = true return @@ -440,9 +522,8 @@ walk: // Outer loop for walking the tree // No handle found. Check if a handle for this path + a // trailing slash exists for trailing slash recommendation - indices := n.indices - for i, max := 0, len(indices); i < max; i++ { - if indices[i] == '/' { + for i, c := range []byte(n.indices) { + if c == '/' { n = n.children[i] value.tsr = (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) @@ -453,218 +534,223 @@ walk: // Outer loop for walking the tree return } - if len(path) > len(prefix) && path[:len(prefix)] == prefix { - path = path[len(prefix):] - // If this node does not have a wildcard (param or catchAll) - // child, we can just look up the next child node and continue - // to walk down the tree + // Nothing found. We can recommend to redirect to the same URL with an + // extra trailing slash if a leaf exists for that path + value.tsr = (path == "/") || + (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && + path == prefix[:len(prefix)-1] && n.handlers != nil) + return + } +} + +// Makes a case-insensitive lookup of the given path and tries to find a handler. +// It can optionally also fix trailing slashes. +// It returns the case-corrected path and a bool indicating whether the lookup +// was successful. +func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]byte, bool) { + const stackBufSize = 128 + + // Use a static sized buffer on the stack in the common case. + // If the path is too long, allocate a buffer on the heap instead. + buf := make([]byte, 0, stackBufSize) + if l := len(path) + 1; l > stackBufSize { + buf = make([]byte, 0, l) + } + + ciPath := n.findCaseInsensitivePathRec( + path, + buf, // Preallocate enough memory for new path + [4]byte{}, // Empty rune buffer + fixTrailingSlash, + ) + + return ciPath, ciPath != nil +} + +// Shift bytes in array by n bytes left +func shiftNRuneBytes(rb [4]byte, n int) [4]byte { + switch n { + case 0: + return rb + case 1: + return [4]byte{rb[1], rb[2], rb[3], 0} + case 2: + return [4]byte{rb[2], rb[3]} + case 3: + return [4]byte{rb[3]} + default: + return [4]byte{} + } +} + +// Recursive case-insensitive lookup function used by n.findCaseInsensitivePath +func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte { + npLen := len(n.path) + +walk: // Outer loop for walking the tree + for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) { + // Add common prefix to result + oldPath := path + path = path[npLen:] + ciPath = append(ciPath, n.path...) + + if len(path) > 0 { + // If this node does not have a wildcard (param or catchAll) child, + // we can just look up the next child node and continue to walk down + // the tree if !n.wildChild { - c := path[0] - indices := n.indices - for i, max := 0, len(indices); i < max; i++ { - if c == indices[i] { - n = n.children[i] - continue walk + // Skip rune bytes already processed + rb = shiftNRuneBytes(rb, npLen) + + if rb[0] != 0 { + // Old rune not finished + idxc := rb[0] + for i, c := range []byte(n.indices) { + if c == idxc { + // continue with child node + n = n.children[i] + npLen = len(n.path) + continue walk + } + } + } else { + // Process a new rune + var rv rune + + // Find rune start. + // Runes are up to 4 byte long, + // -4 would definitely be another rune. + var off int + for max := min(npLen, 3); off < max; off++ { + if i := npLen - off; utf8.RuneStart(oldPath[i]) { + // read rune from cached path + rv, _ = utf8.DecodeRuneInString(oldPath[i:]) + break + } + } + + // Calculate lowercase bytes of current rune + lo := unicode.ToLower(rv) + utf8.EncodeRune(rb[:], lo) + + // Skip already processed bytes + rb = shiftNRuneBytes(rb, off) + + idxc := rb[0] + for i, c := range []byte(n.indices) { + // Lowercase matches + if c == idxc { + // must use a recursive approach since both the + // uppercase byte and the lowercase byte might exist + // as an index + if out := n.children[i].findCaseInsensitivePathRec( + path, ciPath, rb, fixTrailingSlash, + ); out != nil { + return out + } + break + } + } + + // If we found no match, the same for the uppercase rune, + // if it differs + if up := unicode.ToUpper(rv); up != lo { + utf8.EncodeRune(rb[:], up) + rb = shiftNRuneBytes(rb, off) + + idxc := rb[0] + for i, c := range []byte(n.indices) { + // Uppercase matches + if c == idxc { + // Continue with child node + n = n.children[i] + npLen = len(n.path) + continue walk + } + } } } - // Nothing found. - // We can recommend to redirect to the same URL without a - // trailing slash if a leaf exists for that path. - value.tsr = path == "/" && n.handlers != nil - return + // Nothing found. We can recommend to redirect to the same URL + // without a trailing slash if a leaf exists for that path + if fixTrailingSlash && path == "/" && n.handlers != nil { + return ciPath + } + return nil } - // handle wildcard child n = n.children[0] switch n.nType { case param: - // find param end (either '/' or path end) + // Find param end (either '/' or path end) end := 0 for end < len(path) && path[end] != '/' { end++ } - // save param value - if cap(value.params) < int(n.maxParams) { - value.params = make(Params, 0, n.maxParams) - } - i := len(value.params) - value.params = value.params[:i+1] // expand slice within preallocated capacity - value.params[i].Key = n.path[1:] - val := path[:end] - if unescape { - var err error - if value.params[i].Value, err = url.QueryUnescape(val); err != nil { - value.params[i].Value = val // fallback, in case of error - } - } else { - value.params[i].Value = val - } + // Add param value to case insensitive path + ciPath = append(ciPath, path[:end]...) - // we need to go deeper! + // We need to go deeper! if end < len(path) { if len(n.children) > 0 { - path = path[end:] + // Continue with child node n = n.children[0] - continue walk + npLen = len(n.path) + path = path[end:] + continue } // ... but we can't - value.tsr = len(path) == end+1 - return + if fixTrailingSlash && len(path) == end+1 { + return ciPath + } + return nil } - if value.handlers = n.handlers; value.handlers != nil { - value.fullPath = n.fullPath - return + if n.handlers != nil { + return ciPath } - if len(n.children) == 1 { + + if fixTrailingSlash && len(n.children) == 1 { // No handle found. Check if a handle for this path + a - // trailing slash exists for TSR recommendation + // trailing slash exists n = n.children[0] - value.tsr = n.path == "/" && n.handlers != nil - } - return - - case catchAll: - // save param value - if cap(value.params) < int(n.maxParams) { - value.params = make(Params, 0, n.maxParams) - } - i := len(value.params) - value.params = value.params[:i+1] // expand slice within preallocated capacity - value.params[i].Key = n.path[2:] - if unescape { - var err error - if value.params[i].Value, err = url.QueryUnescape(path); err != nil { - value.params[i].Value = path // fallback, in case of error + if n.path == "/" && n.handlers != nil { + return append(ciPath, '/') } - } else { - value.params[i].Value = path } - value.handlers = n.handlers - value.fullPath = n.fullPath - return + return nil + + case catchAll: + return append(ciPath, path...) default: panic("invalid node type") } - } - - // Nothing found. We can recommend to redirect to the same URL with an - // extra trailing slash if a leaf exists for that path - value.tsr = (path == "/") || - (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && - path == prefix[:len(prefix)-1] && n.handlers != nil) - return - } -} - -// findCaseInsensitivePath makes a case-insensitive lookup of the given path and tries to find a handler. -// It can optionally also fix trailing slashes. -// It returns the case-corrected path and a bool indicating whether the lookup -// was successful. -func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) { - ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory - - // Outer loop for walking the tree - for len(path) >= len(n.path) && strings.EqualFold(path[:len(n.path)], n.path) { - path = path[len(n.path):] - ciPath = append(ciPath, n.path...) - - if len(path) == 0 { + } else { // We should have reached the node containing the handle. // Check if this node has a handle registered. if n.handlers != nil { - return ciPath, true + return ciPath } // No handle found. // Try to fix the path by adding a trailing slash if fixTrailingSlash { - for i := 0; i < len(n.indices); i++ { - if n.indices[i] == '/' { + for i, c := range []byte(n.indices) { + if c == '/' { n = n.children[i] if (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) { - return append(ciPath, '/'), true + return append(ciPath, '/') } - return + return nil } } } - return - } - - // If this node does not have a wildcard (param or catchAll) child, - // we can just look up the next child node and continue to walk down - // the tree - if !n.wildChild { - r := unicode.ToLower(rune(path[0])) - for i, index := range n.indices { - // must use recursive approach since both index and - // ToLower(index) could exist. We must check both. - if r == unicode.ToLower(index) { - out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash) - if found { - return append(ciPath, out...), true - } - } - } - - // Nothing found. We can recommend to redirect to the same URL - // without a trailing slash if a leaf exists for that path - found = fixTrailingSlash && path == "/" && n.handlers != nil - return - } - - n = n.children[0] - switch n.nType { - case param: - // Find param end (either '/' or path end) - end := 0 - for end < len(path) && path[end] != '/' { - end++ - } - - // add param value to case insensitive path - ciPath = append(ciPath, path[:end]...) - - // we need to go deeper! - if end < len(path) { - if len(n.children) > 0 { - path = path[end:] - n = n.children[0] - continue - } - - // ... but we can't - if fixTrailingSlash && len(path) == end+1 { - return ciPath, true - } - return - } - - if n.handlers != nil { - return ciPath, true - } - if fixTrailingSlash && len(n.children) == 1 { - // No handle found. Check if a handle for this path + a - // trailing slash exists - n = n.children[0] - if n.path == "/" && n.handlers != nil { - return append(ciPath, '/'), true - } - } - return - - case catchAll: - return append(ciPath, path...), true - - default: - panic("invalid node type") + return nil } } @@ -672,13 +758,12 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa // Try to fix the path by adding / removing a trailing slash if fixTrailingSlash { if path == "/" { - return ciPath, true + return ciPath } - if len(path)+1 == len(n.path) && n.path[len(path)] == '/' && - strings.EqualFold(path, n.path[:len(path)]) && - n.handlers != nil { - return append(ciPath, n.path...), true + if len(path)+1 == npLen && n.path[len(path)] == '/' && + strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handlers != nil { + return append(ciPath, n.path...) } } - return + return nil } diff --git a/tree_test.go b/tree_test.go index 0fe2fe0..1cb4f55 100644 --- a/tree_test.go +++ b/tree_test.go @@ -28,6 +28,11 @@ type testRequests []struct { ps Params } +func getParams() *Params { + ps := make(Params, 0, 20) + return &ps +} + func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) { unescape := false if len(unescapes) >= 1 { @@ -35,7 +40,7 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes .. } for _, request := range requests { - value := tree.getValue(request.path, nil, unescape) + value := tree.getValue(request.path, getParams(), unescape) if value.handlers == nil { if !request.nilHandler { @@ -50,9 +55,12 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes .. } } - if !reflect.DeepEqual(value.params, request.ps) { - t.Errorf("Params mismatch for route '%s'", request.path) + if value.params != nil { + if !reflect.DeepEqual(*value.params, request.ps) { + t.Errorf("Params mismatch for route '%s'", request.path) + } } + } } @@ -76,33 +84,11 @@ func checkPriorities(t *testing.T, n *node) uint32 { return prio } -func checkMaxParams(t *testing.T, n *node) uint8 { - var maxParams uint8 - for i := range n.children { - params := checkMaxParams(t, n.children[i]) - if params > maxParams { - maxParams = params - } - } - if n.nType > root && !n.wildChild { - maxParams++ - } - - if n.maxParams != maxParams { - t.Errorf( - "maxParams mismatch for node '%s': is %d, should be %d", - n.path, n.maxParams, maxParams, - ) - } - - return maxParams -} - func TestCountParams(t *testing.T) { if countParams("/path/:param1/static/*catch-all") != 2 { t.Fail() } - if countParams(strings.Repeat("/:param", 256)) != 255 { + if countParams(strings.Repeat("/:param", 256)) != 256 { t.Fail() } } @@ -142,7 +128,6 @@ func TestTreeAddAndGet(t *testing.T) { }) checkPriorities(t, tree) - checkMaxParams(t, tree) } func TestTreeWildcard(t *testing.T) { @@ -186,7 +171,6 @@ func TestTreeWildcard(t *testing.T) { }) checkPriorities(t, tree) - checkMaxParams(t, tree) } func TestUnescapeParameters(t *testing.T) { @@ -224,7 +208,6 @@ func TestUnescapeParameters(t *testing.T) { }, unescape) checkPriorities(t, tree) - checkMaxParams(t, tree) } func catchPanic(testFunc func()) (recv interface{}) { @@ -323,12 +306,14 @@ func TestTreeDupliatePath(t *testing.T) { } } + //printChildren(tree, "") + checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, {"/doc/", false, "/doc/", nil}, - {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}}, - {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}}, - {"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}}, + {"/src/some/file.png", false, "/src/*filepath", Params{Param{"filepath", "/some/file.png"}}}, + {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{"query", "someth!ng+in+ünìcodé"}}}, + {"/user_gopher", false, "/user_:name", Params{Param{"name", "gopher"}}}, }) } @@ -356,6 +341,8 @@ func TestTreeCatchAllConflict(t *testing.T) { {"/src/*filepath/x", true}, {"/src2/", false}, {"/src2/*filepath/x", true}, + {"/src3/*filepath", false}, + {"/src3/*filepath/x", true}, } testRoutes(t, routes) } @@ -372,7 +359,6 @@ func TestTreeCatchMaxParams(t *testing.T) { tree := &node{} var route = "/cmd/*filepath" tree.addRoute(route, fakeHandler(route)) - checkMaxParams(t, tree) } func TestTreeDoubleWildcard(t *testing.T) { @@ -508,6 +494,9 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { func TestTreeFindCaseInsensitivePath(t *testing.T) { tree := &node{} + longPath := "/l" + strings.Repeat("o", 128) + "ng" + lOngPath := "/l" + strings.Repeat("O", 128) + "ng/" + routes := [...]string{ "/hi", "/b/", @@ -531,6 +520,17 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) { "/doc/go/away", "/no/a", "/no/b", + "/Π", + "/u/apfêl/", + "/u/äpfêl/", + "/u/öpfêl", + "/v/Äpfêl/", + "/v/Öpfêl", + "/w/♬", // 3 byte + "/w/♭/", // 3 byte, last byte differs + "/w/𠜎", // 4 byte + "/w/𠜏/", // 4 byte + longPath, } for _, route := range routes { @@ -609,6 +609,21 @@ func TestTreeFindCaseInsensitivePath(t *testing.T) { {"/DOC/", "/doc", true, true}, {"/NO", "", false, true}, {"/DOC/GO", "", false, true}, + {"/π", "/Π", true, false}, + {"/π/", "/Π", true, true}, + {"/u/ÄPFÊL/", "/u/äpfêl/", true, false}, + {"/u/ÄPFÊL", "/u/äpfêl/", true, true}, + {"/u/ÖPFÊL/", "/u/öpfêl", true, true}, + {"/u/ÖPFÊL", "/u/öpfêl", true, false}, + {"/v/äpfêL/", "/v/Äpfêl/", true, false}, + {"/v/äpfêL", "/v/Äpfêl/", true, true}, + {"/v/öpfêL/", "/v/Öpfêl", true, true}, + {"/v/öpfêL", "/v/Öpfêl", true, false}, + {"/w/♬/", "/w/♬", true, true}, + {"/w/♭", "/w/♭/", true, true}, + {"/w/𠜎/", "/w/𠜎", true, true}, + {"/w/𠜏", "/w/𠜏/", true, true}, + {lOngPath, longPath, true, true}, } // With fixTrailingSlash = true for _, test := range tests { @@ -696,8 +711,7 @@ func TestTreeWildcardConflictEx(t *testing.T) { tree.addRoute(conflict.route, fakeHandler(conflict.route)) }) - if !regexp.MustCompile(fmt.Sprintf("'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'", - conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) { + if !regexp.MustCompile(fmt.Sprintf("'%s' in new path .* conflicts with existing wildcard '%s' in existing prefix '%s'", conflict.segPath, conflict.existSegPath, conflict.existPath)).MatchString(fmt.Sprint(recv)) { t.Fatalf("invalid wildcard conflict error (%v)", recv) } } From a6e8665e42dad83ee275d4adc34a6e507bdd11ed Mon Sep 17 00:00:00 2001 From: vinhha96 Date: Mon, 11 May 2020 12:25:49 +0700 Subject: [PATCH 481/912] fix(tree): reassign fullpath when register new node which the same current node (#2366) * fix(tree): assign fullpath to current node by fullpath of new node if current node the same new node * test(router-test): reverse the order when register router when test func GetFullPath * chg(tree-test): update test case with register new route in TestRouteContextHoldsFullPath Co-authored-by: vinhha Co-authored-by: Bo-Yi Wu --- routes_test.go | 3 +++ tree.go | 1 + 2 files changed, 4 insertions(+) diff --git a/routes_test.go b/routes_test.go index 360ade6..11ff71a 100644 --- a/routes_test.go +++ b/routes_test.go @@ -593,6 +593,9 @@ func TestRouteContextHoldsFullPath(t *testing.T) { "/simple-two/one-two", "/project/:name/build/*params", "/project/:name/bui", + "/user/:id/status", + "/user/:id", + "/user/:id/profile", } for _, route := range routes { diff --git a/tree.go b/tree.go index e3aa919..f528fa3 100644 --- a/tree.go +++ b/tree.go @@ -240,6 +240,7 @@ walk: panic("handlers are already registered for path '" + fullPath + "'") } n.handlers = handlers + n.fullPath = fullPath return } } From 1d5b9badd97ba578f00a0d45ad46834cca5479ca Mon Sep 17 00:00:00 2001 From: thinkerou Date: Thu, 14 May 2020 11:35:14 +0800 Subject: [PATCH 482/912] chore: rename getQueryCache/getFormCache to initQueryCache/initFormCache (#2375) --- context.go | 12 ++++++------ utils.go | 13 ++++++------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/context.go b/context.go index 2ea1a02..a945883 100644 --- a/context.go +++ b/context.go @@ -414,7 +414,7 @@ func (c *Context) QueryArray(key string) []string { return values } -func (c *Context) getQueryCache() { +func (c *Context) initQueryCache() { if c.queryCache == nil { c.queryCache = c.Request.URL.Query() } @@ -423,7 +423,7 @@ func (c *Context) getQueryCache() { // GetQueryArray returns a slice of strings for a given query key, plus // a boolean value whether at least one value exists for the given key. func (c *Context) GetQueryArray(key string) ([]string, bool) { - c.getQueryCache() + c.initQueryCache() if values, ok := c.queryCache[key]; ok && len(values) > 0 { return values, true } @@ -439,7 +439,7 @@ func (c *Context) QueryMap(key string) map[string]string { // GetQueryMap returns a map for a given query key, plus a boolean value // whether at least one value exists for the given key. func (c *Context) GetQueryMap(key string) (map[string]string, bool) { - c.getQueryCache() + c.initQueryCache() return c.get(c.queryCache, key) } @@ -481,7 +481,7 @@ func (c *Context) PostFormArray(key string) []string { return values } -func (c *Context) getFormCache() { +func (c *Context) initFormCache() { if c.formCache == nil { c.formCache = make(url.Values) req := c.Request @@ -497,7 +497,7 @@ func (c *Context) getFormCache() { // GetPostFormArray returns a slice of strings for a given form key, plus // a boolean value whether at least one value exists for the given key. func (c *Context) GetPostFormArray(key string) ([]string, bool) { - c.getFormCache() + c.initFormCache() if values := c.formCache[key]; len(values) > 0 { return values, true } @@ -513,7 +513,7 @@ func (c *Context) PostFormMap(key string) map[string]string { // GetPostFormMap returns a map for a given form key, plus a boolean value // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { - c.getFormCache() + c.initFormCache() return c.get(c.formCache, key) } diff --git a/utils.go b/utils.go index 71b80de..fab3aee 100644 --- a/utils.go +++ b/utils.go @@ -90,13 +90,13 @@ func filterFlags(content string) string { } func chooseData(custom, wildcard interface{}) interface{} { - if custom == nil { - if wildcard == nil { - panic("negotiation config is invalid") - } + if custom != nil { + return custom + } + if wildcard != nil { return wildcard } - return custom + panic("negotiation config is invalid") } func parseAccept(acceptHeader string) []string { @@ -127,8 +127,7 @@ func joinPaths(absolutePath, relativePath string) string { } finalPath := path.Join(absolutePath, relativePath) - appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/' - if appendSlash { + if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' { return finalPath + "/" } return finalPath From b4c8bf1bbe1bf7109c663d34839357341b0d9392 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 17 May 2020 18:11:22 +0800 Subject: [PATCH 483/912] chore(performance): improve countParams (#2378) * update Signed-off-by: Bo-Yi Wu * chore: update * chore: improve countParams performance * update Signed-off-by: Bo-Yi Wu * chore: add comment --- tree.go | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/tree.go b/tree.go index f528fa3..18ada81 100644 --- a/tree.go +++ b/tree.go @@ -5,10 +5,18 @@ package gin import ( + "bytes" "net/url" + "reflect" "strings" "unicode" "unicode/utf8" + "unsafe" +) + +var ( + strColon = []byte(":") + strStar = []byte("*") ) // Param is a single URL parameter, consisting of a key and a value. @@ -72,14 +80,35 @@ func longestCommonPrefix(a, b string) int { return i } +// bytesToStr converts byte slice to a string without memory allocation. +// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ . +// +// Note it may break if string and/or slice header will change +// in the future go versions. +func bytesToStr(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// strToBytes converts string to a byte slice without memory allocation. +// +// Note it may break if string and/or slice header will change +// in the future go versions. +func strToBytes(s string) (b []byte) { + /* #nosec G103 */ + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + /* #nosec G103 */ + sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) + bh.Data = sh.Data + bh.Len = sh.Len + bh.Cap = sh.Len + return b +} + func countParams(path string) uint16 { var n uint - for i := range []byte(path) { - switch path[i] { - case ':', '*': - n++ - } - } + s := strToBytes(path) + n += uint(bytes.Count(s, strColon)) + n += uint(bytes.Count(s, strStar)) return uint16(n) } @@ -163,7 +192,7 @@ walk: n.children = []*node{&child} // []byte for proper unicode char conversion, see #65 - n.indices = string([]byte{n.path[i]}) + n.indices = bytesToStr([]byte{n.path[i]}) n.path = path[:i] n.handlers = nil n.wildChild = false @@ -223,7 +252,7 @@ walk: // Otherwise insert it if c != ':' && c != '*' { // []byte for proper unicode char conversion, see #65 - n.indices += string([]byte{c}) + n.indices += bytesToStr([]byte{c}) child := &node{ fullPath: fullPath, } From acbb3d4f42cb0c3b25e69a37c7080e681b56d74c Mon Sep 17 00:00:00 2001 From: Sudhir Mishra Date: Mon, 18 May 2020 13:20:35 +0530 Subject: [PATCH 484/912] Broken link repo typo fix (#2381) --- BENCHMARKS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BENCHMARKS.md b/BENCHMARKS.md index b164ae0..0f59b50 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -6,7 +6,7 @@ **Date:** May 04th, 2020 **Version:** Gin v1.6.3 **Go Version:** 1.14.2 linux/amd64 -**Source:** [Go HTTP Router Benchmark](https://github/gin-gonic/go-http-routing-benchmark) +**Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark) **Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061) ## Static Routes: 157 From 7bffae1d3dbc2d8256bcf5bb4026a48fc69e4b10 Mon Sep 17 00:00:00 2001 From: bestgopher <84328409@qq.com> Date: Sat, 23 May 2020 22:19:37 +0800 Subject: [PATCH 485/912] Remove some functions that have the same effect as the bytes package (#2387) --- tree.go | 42 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/tree.go b/tree.go index 18ada81..7a80af9 100644 --- a/tree.go +++ b/tree.go @@ -7,11 +7,11 @@ package gin import ( "bytes" "net/url" - "reflect" "strings" "unicode" "unicode/utf8" - "unsafe" + + "github.com/gin-gonic/gin/internal/bytesconv" ) var ( @@ -80,36 +80,12 @@ func longestCommonPrefix(a, b string) int { return i } -// bytesToStr converts byte slice to a string without memory allocation. -// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ . -// -// Note it may break if string and/or slice header will change -// in the future go versions. -func bytesToStr(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} - -// strToBytes converts string to a byte slice without memory allocation. -// -// Note it may break if string and/or slice header will change -// in the future go versions. -func strToBytes(s string) (b []byte) { - /* #nosec G103 */ - bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) - /* #nosec G103 */ - sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) - bh.Data = sh.Data - bh.Len = sh.Len - bh.Cap = sh.Len - return b -} - func countParams(path string) uint16 { - var n uint - s := strToBytes(path) - n += uint(bytes.Count(s, strColon)) - n += uint(bytes.Count(s, strStar)) - return uint16(n) + var n uint16 + s := bytesconv.StringToBytes(path) + n += uint16(bytes.Count(s, strColon)) + n += uint16(bytes.Count(s, strStar)) + return n } type nodeType uint8 @@ -192,7 +168,7 @@ walk: n.children = []*node{&child} // []byte for proper unicode char conversion, see #65 - n.indices = bytesToStr([]byte{n.path[i]}) + n.indices = bytesconv.BytesToString([]byte{n.path[i]}) n.path = path[:i] n.handlers = nil n.wildChild = false @@ -252,7 +228,7 @@ walk: // Otherwise insert it if c != ':' && c != '*' { // []byte for proper unicode char conversion, see #65 - n.indices += bytesToStr([]byte{c}) + n.indices += bytesconv.BytesToString([]byte{c}) child := &node{ fullPath: fullPath, } From 2773ce6e60866643388e6b5089b0a8ed64ea030a Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sat, 23 May 2020 22:52:01 +0800 Subject: [PATCH 486/912] add copyright (#2388) Co-authored-by: Bo-Yi Wu --- internal/bytesconv/bytesconv.go | 4 ++++ internal/bytesconv/bytesconv_test.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv.go index 32c2b59..7b80e33 100644 --- a/internal/bytesconv/bytesconv.go +++ b/internal/bytesconv/bytesconv.go @@ -1,3 +1,7 @@ +// Copyright 2020 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package bytesconv import ( diff --git a/internal/bytesconv/bytesconv_test.go b/internal/bytesconv/bytesconv_test.go index ee2c8ab..eeaad5e 100644 --- a/internal/bytesconv/bytesconv_test.go +++ b/internal/bytesconv/bytesconv_test.go @@ -1,3 +1,7 @@ +// Copyright 2020 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package bytesconv import ( From 922138144359752ff017df26c925bae6525962bc Mon Sep 17 00:00:00 2001 From: bestgopher <84328409@qq.com> Date: Sun, 24 May 2020 10:58:28 +0800 Subject: [PATCH 487/912] remove a unused type SecureJSONPrefix (#2391) --- render/json.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/render/json.go b/render/json.go index 015c0db..4186309 100644 --- a/render/json.go +++ b/render/json.go @@ -41,9 +41,6 @@ type AsciiJSON struct { Data interface{} } -// SecureJSONPrefix is a string which represents SecureJSON prefix. -type SecureJSONPrefix string - // PureJSON contains the given interface object. type PureJSON struct { Data interface{} From 5f261fa7529e62f574831ec7e869a5680ed23b52 Mon Sep 17 00:00:00 2001 From: Miles Date: Sun, 24 May 2020 11:37:32 +0800 Subject: [PATCH 488/912] Add a redirect sample for POST method (#2389) * Add a redirect sample for POST method Refer to issue https://github.com/gin-gonic/gin/issues/444 * put an empty line before 1396 Co-authored-by: Bo-Yi Wu --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 9cc23fc..771b577 100644 --- a/README.md +++ b/README.md @@ -1394,6 +1394,12 @@ r.GET("/test", func(c *gin.Context) { }) ``` +Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444) +```go +r.POST("/test", func(c *gin.Context) { + c.Redirect(http.StatusFound, "/foo") +}) +``` Issuing a Router redirect, use `HandleContext` like below. From c9b853581700d6834cc6e658d6966ac35b52f969 Mon Sep 17 00:00:00 2001 From: bestgopher <84328409@qq.com> Date: Mon, 25 May 2020 20:13:09 +0800 Subject: [PATCH 489/912] Rename some variables (#2393) --- errors.go | 20 ++++++++++---------- fs.go | 6 +++--- routergroup.go | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/errors.go b/errors.go index 25e8ff6..9a31799 100644 --- a/errors.go +++ b/errors.go @@ -55,7 +55,7 @@ func (msg *Error) SetMeta(data interface{}) *Error { // JSON creates a properly formatted JSON func (msg *Error) JSON() interface{} { - json := H{} + jsonData := H{} if msg.Meta != nil { value := reflect.ValueOf(msg.Meta) switch value.Kind() { @@ -63,16 +63,16 @@ func (msg *Error) JSON() interface{} { return msg.Meta case reflect.Map: for _, key := range value.MapKeys() { - json[key.String()] = value.MapIndex(key).Interface() + jsonData[key.String()] = value.MapIndex(key).Interface() } default: - json["meta"] = msg.Meta + jsonData["meta"] = msg.Meta } } - if _, ok := json["error"]; !ok { - json["error"] = msg.Error() + if _, ok := jsonData["error"]; !ok { + jsonData["error"] = msg.Error() } - return json + return jsonData } // MarshalJSON implements the json.Marshaller interface. @@ -135,17 +135,17 @@ func (a errorMsgs) Errors() []string { } func (a errorMsgs) JSON() interface{} { - switch len(a) { + switch length := len(a); length { case 0: return nil case 1: return a.Last().JSON() default: - json := make([]interface{}, len(a)) + jsonData := make([]interface{}, length) for i, err := range a { - json[i] = err.JSON() + jsonData[i] = err.JSON() } - return json + return jsonData } } diff --git a/fs.go b/fs.go index 7a6738a..007d9b7 100644 --- a/fs.go +++ b/fs.go @@ -9,7 +9,7 @@ import ( "os" ) -type onlyfilesFS struct { +type onlyFilesFS struct { fs http.FileSystem } @@ -26,11 +26,11 @@ func Dir(root string, listDirectory bool) http.FileSystem { if listDirectory { return fs } - return &onlyfilesFS{fs} + return &onlyFilesFS{fs} } // Open conforms to http.Filesystem. -func (fs onlyfilesFS) Open(name string) (http.File, error) { +func (fs onlyFilesFS) Open(name string) (http.File, error) { f, err := fs.fs.Open(name) if err != nil { return nil, err diff --git a/routergroup.go b/routergroup.go index 9ff7c03..15d9930 100644 --- a/routergroup.go +++ b/routergroup.go @@ -187,7 +187,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { - if _, nolisting := fs.(*onlyfilesFS); nolisting { + if _, noListing := fs.(*onlyFilesFS); noListing { c.Writer.WriteHeader(http.StatusNotFound) } From 5e40c1d49c21bf989e8d54dbd555086f06d4fb8a Mon Sep 17 00:00:00 2001 From: Vas N Date: Mon, 25 May 2020 14:47:06 +0100 Subject: [PATCH 490/912] DebugPrintRouteFunc() unit test (#2395) Co-authored-by: thinkerou --- debug_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/debug_test.go b/debug_test.go index d707b4b..d8cd5d1 100644 --- a/debug_test.go +++ b/debug_test.go @@ -7,6 +7,7 @@ package gin import ( "bytes" "errors" + "fmt" "html/template" "io" "log" @@ -64,6 +65,18 @@ func TestDebugPrintRoutes(t *testing.T) { assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re) } +func TestDebugPrintRouteFunc(t *testing.T) { + DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + fmt.Fprintf(DefaultWriter, "[GIN-debug] %-6s %-40s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + re := captureOutput(t, func() { + SetMode(DebugMode) + debugPrintRoute("GET", "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest}) + SetMode(TestMode) + }) + assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param1/:param2 --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re) +} + func TestDebugPrintLoadTemplate(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) From 4cabdd303fe38b6b53e83a6aa04d0468a71c0139 Mon Sep 17 00:00:00 2001 From: Johnny Dallas Date: Wed, 8 Jul 2020 18:40:00 -0700 Subject: [PATCH 491/912] Add CustomRecovery builtin middleware (#2322) * Add CustomRecovery and CustomRecoveryWithWriter methods * add CustomRecovery example to README * add test for CustomRecovery * support RecoveryWithWriter(io.Writer, ...RecoveryFunc) --- README.md | 33 +++++++++++++++ recovery.go | 27 ++++++++++-- recovery_test.go | 106 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 161 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 771b577..7ff5999 100644 --- a/README.md +++ b/README.md @@ -496,6 +496,39 @@ func main() { } ``` +### Custom Recovery behavior +```go +func main() { + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { + if err, ok := recovered.(string); ok { + c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) + } + c.AbortWithStatus(http.StatusInternalServerError) + })) + + r.GET("/panic", func(c *gin.Context) { + // panic with a string -- the custom middleware could save this to a database or report it to the user + panic("foo") + }) + + r.GET("/", func(c *gin.Context) { + c.String(http.StatusOK, "ohai") + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + ### How to write log file ```go func main() { diff --git a/recovery.go b/recovery.go index 8cf0932..d02b829 100644 --- a/recovery.go +++ b/recovery.go @@ -26,13 +26,29 @@ var ( slash = []byte("/") ) +// RecoveryFunc defines the function passable to CustomRecovery. +type RecoveryFunc func(c *Context, err interface{}) + // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one. func Recovery() HandlerFunc { return RecoveryWithWriter(DefaultErrorWriter) } +//CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it. +func CustomRecovery(handle RecoveryFunc) HandlerFunc { + return RecoveryWithWriter(DefaultErrorWriter, handle) +} + // RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one. -func RecoveryWithWriter(out io.Writer) HandlerFunc { +func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc { + if len(recovery) > 0 { + return CustomRecoveryWithWriter(out, recovery[0]) + } + return CustomRecoveryWithWriter(out, defaultHandleRecovery) +} + +// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it. +func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { var logger *log.Logger if out != nil { logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags) @@ -70,13 +86,12 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { timeFormat(time.Now()), err, stack, reset) } } - - // If the connection is dead, we can't write a status to it. if brokenPipe { + // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: errcheck c.Abort() } else { - c.AbortWithStatus(http.StatusInternalServerError) + handle(c, err) } } }() @@ -84,6 +99,10 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { } } +func defaultHandleRecovery(c *Context, err interface{}) { + c.AbortWithStatus(http.StatusInternalServerError) +} + // stack returns a nicely formatted stack frame, skipping skip frames. func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data diff --git a/recovery_test.go b/recovery_test.go index 21a0a48..6cc2a47 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -62,7 +62,7 @@ func TestPanicInHandler(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "panic recovered") assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") - assert.Contains(t, buffer.String(), "TestPanicInHandler") + assert.Contains(t, buffer.String(), t.Name()) assert.NotContains(t, buffer.String(), "GET /recovery") // Debug mode prints the request @@ -144,3 +144,107 @@ func TestPanicWithBrokenPipe(t *testing.T) { }) } } + +func TestCustomRecoveryWithWriter(t *testing.T) { + errBuffer := new(bytes.Buffer) + buffer := new(bytes.Buffer) + router := New() + handleRecovery := func(c *Context, err interface{}) { + errBuffer.WriteString(err.(string)) + c.AbortWithStatus(http.StatusBadRequest) + } + router.Use(CustomRecoveryWithWriter(buffer, handleRecovery)) + router.GET("/recovery", func(_ *Context) { + panic("Oupps, Houston, we have a problem") + }) + // RUN + w := performRequest(router, "GET", "/recovery") + // TEST + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, buffer.String(), "panic recovered") + assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") + assert.Contains(t, buffer.String(), t.Name()) + assert.NotContains(t, buffer.String(), "GET /recovery") + + // Debug mode prints the request + SetMode(DebugMode) + // RUN + w = performRequest(router, "GET", "/recovery") + // TEST + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, buffer.String(), "GET /recovery") + + assert.Equal(t, strings.Repeat("Oupps, Houston, we have a problem", 2), errBuffer.String()) + + SetMode(TestMode) +} + +func TestCustomRecovery(t *testing.T) { + errBuffer := new(bytes.Buffer) + buffer := new(bytes.Buffer) + router := New() + DefaultErrorWriter = buffer + handleRecovery := func(c *Context, err interface{}) { + errBuffer.WriteString(err.(string)) + c.AbortWithStatus(http.StatusBadRequest) + } + router.Use(CustomRecovery(handleRecovery)) + router.GET("/recovery", func(_ *Context) { + panic("Oupps, Houston, we have a problem") + }) + // RUN + w := performRequest(router, "GET", "/recovery") + // TEST + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, buffer.String(), "panic recovered") + assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") + assert.Contains(t, buffer.String(), t.Name()) + assert.NotContains(t, buffer.String(), "GET /recovery") + + // Debug mode prints the request + SetMode(DebugMode) + // RUN + w = performRequest(router, "GET", "/recovery") + // TEST + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, buffer.String(), "GET /recovery") + + assert.Equal(t, strings.Repeat("Oupps, Houston, we have a problem", 2), errBuffer.String()) + + SetMode(TestMode) +} + +func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { + errBuffer := new(bytes.Buffer) + buffer := new(bytes.Buffer) + router := New() + DefaultErrorWriter = buffer + handleRecovery := func(c *Context, err interface{}) { + errBuffer.WriteString(err.(string)) + c.AbortWithStatus(http.StatusBadRequest) + } + router.Use(RecoveryWithWriter(DefaultErrorWriter, handleRecovery)) + router.GET("/recovery", func(_ *Context) { + panic("Oupps, Houston, we have a problem") + }) + // RUN + w := performRequest(router, "GET", "/recovery") + // TEST + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, buffer.String(), "panic recovered") + assert.Contains(t, buffer.String(), "Oupps, Houston, we have a problem") + assert.Contains(t, buffer.String(), t.Name()) + assert.NotContains(t, buffer.String(), "GET /recovery") + + // Debug mode prints the request + SetMode(DebugMode) + // RUN + w = performRequest(router, "GET", "/recovery") + // TEST + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.Contains(t, buffer.String(), "GET /recovery") + + assert.Equal(t, strings.Repeat("Oupps, Houston, we have a problem", 2), errBuffer.String()) + + SetMode(TestMode) +} From 44e78a78d434aa3462e0578f1483873214a7fe81 Mon Sep 17 00:00:00 2001 From: Hiroyuki Tanaka Date: Sat, 1 Aug 2020 16:03:33 +0900 Subject: [PATCH 492/912] README: Change badge to pkg.go.dev (#2449) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ff5999..5ce9d8e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) -[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) +[![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) From c6d6df6d5ada990c902c51a54b9c4c6f21f87840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A3=AE=20=E5=84=AA=E5=A4=AA?= <59682979+uta-mori@users.noreply.github.com> Date: Sat, 1 Aug 2020 16:26:29 +0900 Subject: [PATCH 493/912] Docs: Update README.md Custom Validator sample code (#2448) Co-authored-by: Bo-Yi Wu --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5ce9d8e..fc1d01c 100644 --- a/README.md +++ b/README.md @@ -758,12 +758,12 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "gopkg.in/go-playground/validator.v10" + "github.com/go-playground/validator/v10" ) // Booking contains binded and validated data. type Booking struct { - CheckIn time.Time `form:"check_in" binding:"required" time_format:"2006-01-02"` + 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"` } @@ -800,11 +800,14 @@ func getBookable(c *gin.Context) { ``` ```console -$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17" +$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17" {"message":"Booking dates are valid!"} -$ curl "localhost:8085/bookable?check_in=2018-03-10&check_out=2018-03-09" +$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09" {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} + +$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10" +{"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 registered this way. From cf8b583db420257f5ed2c751073e18028ba47551 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Tue, 4 Aug 2020 09:04:06 +1000 Subject: [PATCH 494/912] Fix spelling (#2451) --- CHANGELOG.md | 6 +++--- binding/form_mapping_test.go | 2 +- context_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 592c2ab..3ac51ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,14 @@ ## Gin v1.6.2 -### BUFIXES +### BUGFIXES * fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305) ### ENHANCEMENTS * Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306) ## Gin v1.6.1 -### BUFIXES +### BUGFIXES * Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294) ## Gin v1.6.0 @@ -25,7 +25,7 @@ * drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148) * Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615) ### FEATURES - * add yaml negotitation [#2220](https://github.com/gin-gonic/gin/pull/2220) + * add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220) * FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112) ### BUGFIXES * Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280) diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 2a56037..2675d46 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -190,7 +190,7 @@ func TestMappingTime(t *testing.T) { assert.Error(t, err) } -func TestMapiingTimeDuration(t *testing.T) { +func TestMappingTimeDuration(t *testing.T) { var s struct { D time.Duration } diff --git a/context_test.go b/context_test.go index ce077bc..b53bf92 100644 --- a/context_test.go +++ b/context_test.go @@ -940,7 +940,7 @@ func TestContextRenderNoContentHTMLString(t *testing.T) { assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } -// TestContextData tests that the response can be written from `bytesting` +// TestContextData tests that the response can be written from `bytestring` // with specified MIME type func TestContextRenderData(t *testing.T) { w := httptest.NewRecorder() From 30b5f7e2d7b32a498ba12f3fe1ead105e63f7f7c Mon Sep 17 00:00:00 2001 From: lantw44 Date: Sat, 8 Aug 2020 17:31:08 +0800 Subject: [PATCH 495/912] binding: avoid 2038 problem on 32-bit architectures (#2450) Function setTimeField calls strconv.ParseInt with bit size 0 when parsing Unix time, which means it is equivalent to specifying 32 on 32-bit architectures. This causes the function to suffer from the year 2038 problem. To fix it and keep the behavior the same on both 32-bit and 64-bit architectures, explicitly specify bit size 64. Co-authored-by: thinkerou --- binding/form_mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index b81ad19..f0913ea 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -270,7 +270,7 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val switch tf := strings.ToLower(timeFormat); tf { case "unix", "unixnano": - tv, err := strconv.ParseInt(val, 10, 0) + tv, err := strconv.ParseInt(val, 10, 64) if err != nil { return err } From 815e1ce281f873c62cdbe05d60b5b2ef9d20ed6b Mon Sep 17 00:00:00 2001 From: Florian Polster Date: Sat, 8 Aug 2020 14:32:19 +0200 Subject: [PATCH 496/912] Prevent panic in Context.GetQuery() when there is no Request (#2412) Co-authored-by: thinkerou --- context.go | 6 +++++- context_test.go | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index a945883..95b1807 100644 --- a/context.go +++ b/context.go @@ -416,7 +416,11 @@ func (c *Context) QueryArray(key string) []string { func (c *Context) initQueryCache() { if c.queryCache == nil { - c.queryCache = c.Request.URL.Query() + if c.Request != nil { + c.queryCache = c.Request.URL.Query() + } else { + c.queryCache = url.Values{} + } } } diff --git a/context_test.go b/context_test.go index b53bf92..1a5a3c5 100644 --- a/context_test.go +++ b/context_test.go @@ -410,6 +410,21 @@ func TestContextQuery(t *testing.T) { assert.Empty(t, c.PostForm("foo")) } +func TestContextDefaultQueryOnEmptyRequest(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) // here c.Request == nil + assert.NotPanics(t, func() { + value, ok := c.GetQuery("NoKey") + assert.False(t, ok) + assert.Empty(t, value) + }) + assert.NotPanics(t, func() { + assert.Equal(t, "nada", c.DefaultQuery("NoKey", "nada")) + }) + assert.NotPanics(t, func() { + assert.Empty(t, c.Query("NoKey")) + }) +} + func TestContextQueryAndPostForm(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") From b94d23d1b48d7b5078035ddbab0c3b5df138b827 Mon Sep 17 00:00:00 2001 From: AllinGo Date: Wed, 12 Aug 2020 09:28:51 +0800 Subject: [PATCH 497/912] support go 1.15 (#2463) --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6680a5b..d7086b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,10 @@ matrix: - go: 1.14.x env: - TESTTAGS=nomsgpack + - go: 1.15.x + - go: 1.15.x + env: + - TESTTAGS=nomsgpack - go: master git: From 0304ee96edb2f7f863d5e8337073cade0327af4a Mon Sep 17 00:00:00 2001 From: kaiiak Date: Tue, 1 Sep 2020 09:33:54 +0800 Subject: [PATCH 498/912] Add GetUint and GetUint64 method on gin.context (#2487) --- context.go | 16 ++++++++++++++++ context_test.go | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/context.go b/context.go index 95b1807..216ceca 100644 --- a/context.go +++ b/context.go @@ -295,6 +295,22 @@ func (c *Context) GetInt64(key string) (i64 int64) { return } +// GetUint returns the value associated with the key as an unsigned integer. +func (c *Context) GetUint(key string) (ui uint) { + if val, ok := c.Get(key); ok && val != nil { + ui, _ = val.(uint) + } + return +} + +// GetUint64 returns the value associated with the key as an unsigned integer. +func (c *Context) GetUint64(key string) (ui64 uint64) { + if val, ok := c.Get(key); ok && val != nil { + ui64, _ = val.(uint64) + } + return +} + // GetFloat64 returns the value associated with the key as a float64. func (c *Context) GetFloat64(key string) (f64 float64) { if val, ok := c.Get(key); ok && val != nil { diff --git a/context_test.go b/context_test.go index 1a5a3c5..e2f8de0 100644 --- a/context_test.go +++ b/context_test.go @@ -261,6 +261,18 @@ func TestContextGetInt64(t *testing.T) { assert.Equal(t, int64(42424242424242), c.GetInt64("int64")) } +func TestContextGetUint(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("uint", uint(1)) + assert.Equal(t, uint(1), c.GetUint("uint")) +} + +func TestContextGetUint64(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Set("uint64", uint64(18446744073709551615)) + assert.Equal(t, uint64(18446744073709551615), c.GetUint64("uint64")) +} + func TestContextGetFloat64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("float64", 4.2) From b860d8672daea919f988ed83df94f28ac4c9cc5f Mon Sep 17 00:00:00 2001 From: Dennis Cho <47404603+forest747@users.noreply.github.com> Date: Thu, 3 Sep 2020 00:15:25 +0900 Subject: [PATCH 499/912] Fix typo (#2489) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc1d01c..a40d637 100644 --- a/README.md +++ b/README.md @@ -340,7 +340,7 @@ func main() { ``` ``` -ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] +ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou] ``` ### Upload files From 1f232c7f47fb0675bcf0b334c8c4ab252c0dba86 Mon Sep 17 00:00:00 2001 From: yugu Date: Wed, 9 Sep 2020 20:00:44 +0800 Subject: [PATCH 500/912] docs:close the body of the response (#2494) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a40d637..0515e99 100644 --- a/README.md +++ b/README.md @@ -1255,6 +1255,7 @@ func main() { } reader := response.Body + defer reader.Close() contentLength := response.ContentLength contentType := response.Header.Get("Content-Type") From 3100b7cb05a8072b76d31686d8a7b4f9b12df4be Mon Sep 17 00:00:00 2001 From: John Bampton Date: Mon, 14 Sep 2020 12:40:20 +1000 Subject: [PATCH 501/912] Fix spelling (#2498) --- context.go | 2 +- context_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 216ceca..5b60d8b 100644 --- a/context.go +++ b/context.go @@ -973,7 +973,7 @@ func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) } -// FileFromFS writes the specified file from http.FileSytem into the body stream in an efficient way. +// FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way. func (c *Context) FileFromFS(filepath string, fs http.FileSystem) { defer func(old string) { c.Request.URL.Path = old diff --git a/context_test.go b/context_test.go index e2f8de0..8e1e3b5 100644 --- a/context_test.go +++ b/context_test.go @@ -1282,7 +1282,7 @@ func TestContextIsAborted(t *testing.T) { assert.True(t, c.IsAborted()) } -// TestContextData tests that the response can be written from `bytesting` +// TestContextData tests that the response can be written from `bytestring` // with specified MIME type func TestContextAbortWithStatus(t *testing.T) { w := httptest.NewRecorder() From 540b1eff7069128df5d95e09968569f2266d9a6a Mon Sep 17 00:00:00 2001 From: eudore <30709860+eudore@users.noreply.github.com> Date: Fri, 25 Sep 2020 09:45:17 +0800 Subject: [PATCH 502/912] update content-disposition header to MIME-style (#2512) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 5b60d8b..71fb593 100644 --- a/context.go +++ b/context.go @@ -987,7 +987,7 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) { // FileAttachment writes the specified file into the body stream in an efficient way // On the client side, the file will typically be downloaded with the given filename func (c *Context) FileAttachment(filepath, filename string) { - c.Writer.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) http.ServeFile(c.Writer, c.Request, filepath) } From 129796610085cd37bb1f48973363e0cb381664b6 Mon Sep 17 00:00:00 2001 From: xyb Date: Thu, 15 Oct 2020 13:55:57 +0800 Subject: [PATCH 503/912] use IndexByte replace Split to improve performance (#2500) Co-authored-by: yonbiaoxiao Co-authored-by: Bo-Yi Wu --- utils.go | 5 ++++- utils_test.go | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/utils.go b/utils.go index fab3aee..c32f0ee 100644 --- a/utils.go +++ b/utils.go @@ -103,7 +103,10 @@ func parseAccept(acceptHeader string) []string { parts := strings.Split(acceptHeader, ",") out := make([]string, 0, len(parts)) for _, part := range parts { - if part = strings.TrimSpace(strings.Split(part, ";")[0]); part != "" { + if i := strings.IndexByte(part, ';'); i > 0 { + part = part[:i] + } + if part = strings.TrimSpace(part); part != "" { out = append(out, part) } } diff --git a/utils_test.go b/utils_test.go index 9b57c57..cc486c3 100644 --- a/utils_test.go +++ b/utils_test.go @@ -18,6 +18,12 @@ func init() { SetMode(TestMode) } +func BenchmarkParseAccept(b *testing.B) { + for i := 0; i < b.N; i++ { + parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8") + } +} + type testStruct struct { T *testing.T } From a7a6986d73f69f8f51d69db9a95aaddf6382e9ea Mon Sep 17 00:00:00 2001 From: Zach Newburgh Date: Thu, 15 Oct 2020 10:41:35 -0400 Subject: [PATCH 504/912] fix: print headers without Authorization header on broken pipe (#2528) Co-authored-by: thinkerou --- recovery.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/recovery.go b/recovery.go index d02b829..563f5aa 100644 --- a/recovery.go +++ b/recovery.go @@ -76,11 +76,12 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { headers[idx] = current[0] + ": *" } } + headersToStr := strings.Join(headers, "\r\n") if brokenPipe { - logger.Printf("%s\n%s%s", err, string(httpRequest), reset) + logger.Printf("%s\n%s%s", err, headersToStr, reset) } else if IsDebugging() { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", - timeFormat(time.Now()), strings.Join(headers, "\r\n"), err, stack, reset) + timeFormat(time.Now()), headersToStr, err, stack, reset) } else { logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", timeFormat(time.Now()), err, stack, reset) From d541085b59975edc285e98b6c19cea6bfed7d0a9 Mon Sep 17 00:00:00 2001 From: Zasda Yusuf Mikail Date: Fri, 16 Oct 2020 08:32:10 +0700 Subject: [PATCH 505/912] Add some missing dots on README (#2519) Signed-off-by: Zasda Yusuf Mikail Co-authored-by: Bo-Yi Wu --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0515e99..7e8359e 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,8 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr - [x] Zero allocation router. - [x] Still the fastest http router and framework. From routing to writing. -- [x] Complete suite of unit tests -- [x] Battle tested +- [x] Complete suite of unit tests. +- [x] Battle tested. - [x] API frozen, new releases will not break your code. ## Build with [jsoniter](https://github.com/json-iterator/go) From c83a1cca0a804261cfce5222b2bb35e3d9a46e48 Mon Sep 17 00:00:00 2001 From: xyb Date: Fri, 16 Oct 2020 18:32:33 +0800 Subject: [PATCH 506/912] reduce allocs and improve the render `WriteString` (#2508) Co-authored-by: yonbiaoxiao Co-authored-by: Bo-Yi Wu Co-authored-by: thinkerou --- render/text.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/render/text.go b/render/text.go index 30f5f53..461b720 100644 --- a/render/text.go +++ b/render/text.go @@ -7,6 +7,8 @@ package render import ( "fmt" "net/http" + + "github.com/gin-gonic/gin/internal/bytesconv" ) // String contains the given interface object slice and its format. @@ -34,6 +36,6 @@ func WriteString(w http.ResponseWriter, format string, data []interface{}) (err _, err = fmt.Fprintf(w, format, data...) return } - _, err = w.Write([]byte(format)) + _, err = w.Write(bytesconv.StringToBytes(format)) return } From f969bfaf50d095713e17a10edd0e3534ba2fa9f6 Mon Sep 17 00:00:00 2001 From: Georges Varouchas Date: Sat, 17 Oct 2020 15:22:37 +0200 Subject: [PATCH 507/912] implement ".Unwrap() error" on Error type (#2525) (#2526) Co-authored-by: Bo-Yi Wu Co-authored-by: thinkerou --- errors.go | 5 +++++ errors_1.13_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 errors_1.13_test.go diff --git a/errors.go b/errors.go index 9a31799..0f276c1 100644 --- a/errors.go +++ b/errors.go @@ -90,6 +90,11 @@ func (msg *Error) IsType(flags ErrorType) bool { return (msg.Type & flags) > 0 } +// Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap() +func (msg *Error) Unwrap() error { + return msg.Err +} + // ByType returns a readonly copy filtered the byte. // ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic. func (a errorMsgs) ByType(typ ErrorType) errorMsgs { diff --git a/errors_1.13_test.go b/errors_1.13_test.go new file mode 100644 index 0000000..a8f9a94 --- /dev/null +++ b/errors_1.13_test.go @@ -0,0 +1,33 @@ +// +build go1.13 + +package gin + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +type TestErr string + +func (e TestErr) Error() string { return string(e) } + +// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()". +// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13, +// hence the "// +build go1.13" directive at the beginning of this file. +func TestErrorUnwrap(t *testing.T) { + innerErr := TestErr("somme error") + + // 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr + err := fmt.Errorf("wrapped: %w", &Error{ + Err: innerErr, + Type: ErrorTypeAny, + }) + + // check that 'errors.Is()' and 'errors.As()' behave as expected : + assert.True(t, errors.Is(err, innerErr)) + var testErr TestErr + assert.True(t, errors.As(err, &testErr)) +} From 7e444c6f59340cb7b8d940e91a8dfcc7ce1e47c2 Mon Sep 17 00:00:00 2001 From: Peperoncino <2wua4nlyi@gmail.com> Date: Wed, 21 Oct 2020 10:36:01 +0900 Subject: [PATCH 508/912] upgrade go-validator to v10.4.1 (#2536) --- go.mod | 2 +- go.sum | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cfaee74..884ff85 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.2.0 + github.com/go-playground/validator/v10 v10.4.1 github.com/golang/protobuf v1.3.3 github.com/json-iterator/go v1.1.9 github.com/mattn/go-isatty v0.0.12 diff --git a/go.sum b/go.sum index 4c14fb8..a64b331 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -34,8 +34,15 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 16cd8cdd4ef9257b1e86b5119df1536de71c6a87 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 25 Oct 2020 17:08:30 +0800 Subject: [PATCH 509/912] ci: romove go1.11 for gin1.7 (#2540) --- .travis.yml | 2 -- README.md | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d7086b3..0795665 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: go matrix: fast_finish: true include: - - go: 1.11.x - env: GO111MODULE=on - go: 1.12.x env: GO111MODULE=on - go: 1.13.x diff --git a/README.md b/README.md index 7e8359e..18b1943 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. The first need [Go](https://golang.org/) installed (**version 1.11+ is required**), then you can use the below Go command to install Gin. +1. The first need [Go](https://golang.org/) installed (**version 1.12+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin From 65ed60ed1334140d8583a3d0533cea76c66b69fe Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Fri, 30 Oct 2020 23:20:47 +0000 Subject: [PATCH 510/912] Allow bind with a map[string]string (#2484) Co-authored-by: thinkerou --- binding/binding_test.go | 93 +++++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 41 ++++++++++++++++++ binding/json_test.go | 9 ++++ 3 files changed, 143 insertions(+) diff --git a/binding/binding_test.go b/binding/binding_test.go index 4424bab..c354be9 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -13,6 +13,7 @@ import ( "mime/multipart" "net/http" "os" + "reflect" "strconv" "strings" "testing" @@ -200,6 +201,12 @@ func TestBindingJSONDisallowUnknownFields(t *testing.T) { `{"foo": "bar"}`, `{"foo": "bar", "what": "this"}`) } +func TestBindingJSONStringMap(t *testing.T) { + testBodyBindingStringMap(t, JSON, + "/", "/", + `{"foo": "bar", "hello": "world"}`, `{"num": 2}`) +} + func TestBindingForm(t *testing.T) { testFormBinding(t, "POST", "/", "/", @@ -336,6 +343,37 @@ func TestBindingFormForType(t *testing.T) { "", "", "StructPointer") } +func TestBindingFormStringMap(t *testing.T) { + testBodyBindingStringMap(t, Form, + "/", "", + `foo=bar&hello=world`, "") + // Should pick the last value + testBodyBindingStringMap(t, Form, + "/", "", + `foo=something&foo=bar&hello=world`, "") +} + +func TestBindingFormStringSliceMap(t *testing.T) { + obj := make(map[string][]string) + req := requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") + req.Header.Add("Content-Type", MIMEPOSTForm) + err := Form.Bind(req, &obj) + assert.NoError(t, err) + assert.NotNil(t, obj) + assert.Len(t, obj, 2) + target := map[string][]string{ + "foo": {"something", "bar"}, + "hello": {"world"}, + } + assert.True(t, reflect.DeepEqual(obj, target)) + + objInvalid := make(map[string][]int) + req = requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") + req.Header.Add("Content-Type", MIMEPOSTForm) + err = Form.Bind(req, &objInvalid) + assert.Error(t, err) +} + func TestBindingQuery(t *testing.T) { testQueryBinding(t, "POST", "/?foo=bar&bar=foo", "/", @@ -366,6 +404,28 @@ func TestBindingQueryBoolFail(t *testing.T) { "bool_foo=unused", "") } +func TestBindingQueryStringMap(t *testing.T) { + b := Query + + obj := make(map[string]string) + req := requestWithBody("GET", "/?foo=bar&hello=world", "") + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.NotNil(t, obj) + assert.Len(t, obj, 2) + assert.Equal(t, "bar", obj["foo"]) + assert.Equal(t, "world", obj["hello"]) + + obj = make(map[string]string) + req = requestWithBody("GET", "/?foo=bar&foo=2&hello=world", "") // should pick last + err = b.Bind(req, &obj) + assert.NoError(t, err) + assert.NotNil(t, obj) + assert.Len(t, obj, 2) + assert.Equal(t, "2", obj["foo"]) + assert.Equal(t, "world", obj["hello"]) +} + func TestBindingXML(t *testing.T) { testBodyBinding(t, XML, "xml", @@ -387,6 +447,13 @@ func TestBindingYAML(t *testing.T) { `foo: bar`, `bar: foo`) } +func TestBindingYAMLStringMap(t *testing.T) { + // YAML is a superset of JSON, so the test below is JSON (to avoid newlines) + testBodyBindingStringMap(t, YAML, + "/", "/", + `{"foo": "bar", "hello": "world"}`, `{"nested": {"foo": "bar"}}`) +} + func TestBindingYAMLFail(t *testing.T) { testBodyBindingFail(t, YAML, "yaml", @@ -1114,6 +1181,32 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody assert.Error(t, err) } +func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) { + obj := make(map[string]string) + req := requestWithBody("POST", path, body) + if b.Name() == "form" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.NotNil(t, obj) + assert.Len(t, obj, 2) + assert.Equal(t, "bar", obj["foo"]) + assert.Equal(t, "world", obj["hello"]) + + if badPath != "" && badBody != "" { + obj = make(map[string]string) + req = requestWithBody("POST", badPath, badBody) + err = b.Bind(req, &obj) + assert.Error(t, err) + } + + objInt := make(map[string]int) + req = requestWithBody("POST", path, body) + err = b.Bind(req, &objInt) + assert.Error(t, err) +} + func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index f0913ea..2f4e45b 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -29,6 +29,21 @@ func mapForm(ptr interface{}, form map[string][]string) error { var emptyField = reflect.StructField{} func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { + // Check if ptr is a map + ptrVal := reflect.ValueOf(ptr) + var pointed interface{} + if ptrVal.Kind() == reflect.Ptr { + ptrVal = ptrVal.Elem() + pointed = ptrVal.Interface() + } + if ptrVal.Kind() == reflect.Map && + ptrVal.Type().Key().Kind() == reflect.String { + if pointed != nil { + ptr = pointed + } + return setFormMap(ptr, form) + } + return mappingByPtr(ptr, formSource(form), tag) } @@ -349,3 +364,29 @@ func head(str, sep string) (head string, tail string) { } return str[:idx], str[idx+len(sep):] } + +func setFormMap(ptr interface{}, form map[string][]string) error { + el := reflect.TypeOf(ptr).Elem() + + if el.Kind() == reflect.Slice { + ptrMap, ok := ptr.(map[string][]string) + if !ok { + return errors.New("cannot convert to map slices of strings") + } + for k, v := range form { + ptrMap[k] = v + } + + return nil + } + + ptrMap, ok := ptr.(map[string]string) + if !ok { + return errors.New("cannot convert to map of strings") + } + for k, v := range form { + ptrMap[k] = v[len(v)-1] // pick last + } + + return nil +} diff --git a/binding/json_test.go b/binding/json_test.go index cae4ccc..fbd5c52 100644 --- a/binding/json_test.go +++ b/binding/json_test.go @@ -19,3 +19,12 @@ func TestJSONBindingBindBody(t *testing.T) { require.NoError(t, err) assert.Equal(t, "FOO", s.Foo) } + +func TestJSONBindingBindBodyMap(t *testing.T) { + s := make(map[string]string) + err := jsonBinding{}.BindBody([]byte(`{"foo": "FOO","hello":"world"}`), &s) + require.NoError(t, err) + assert.Len(t, s, 2) + assert.Equal(t, "FOO", s["foo"]) + assert.Equal(t, "world", s["hello"]) +} From 7742ff50e0a05d079a0c468ccfbf7c6ecfe2414b Mon Sep 17 00:00:00 2001 From: "An Xiao (Luffy)" Date: Wed, 11 Nov 2020 09:41:35 +0800 Subject: [PATCH 511/912] Fix typos in context.go (#2551) --- context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 71fb593..3d6b56d 100644 --- a/context.go +++ b/context.go @@ -891,7 +891,7 @@ func (c *Context) SecureJSON(code int, obj interface{}) { } // JSONP serializes the given struct as JSON into the response body. -// It add padding to response body to request data from a server residing in a different domain than the client. +// It adds padding to response body to request data from a server residing in a different domain than the client. // It also sets the Content-Type as "application/javascript". func (c *Context) JSONP(code int, obj interface{}) { callback := c.DefaultQuery("callback", "") @@ -968,7 +968,7 @@ func (c *Context) DataFromReader(code int, contentLength int64, contentType stri }) } -// File writes the specified file into the body stream in a efficient way. +// File writes the specified file into the body stream in an efficient way. func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) } From 3b5e861bb1c7f93424ab861dd779de168cb4c624 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 3 Jan 2021 21:14:56 +0800 Subject: [PATCH 512/912] fix compile error from #2572 (#2600) --- internal/bytesconv/bytesconv.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv.go index 7b80e33..fdad201 100644 --- a/internal/bytesconv/bytesconv.go +++ b/internal/bytesconv/bytesconv.go @@ -5,16 +5,17 @@ package bytesconv import ( - "reflect" "unsafe" ) // StringToBytes converts string to byte slice without a memory allocation. func StringToBytes(s string) (b []byte) { - sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) - bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) - bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len - return b + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) } // BytesToString converts byte slice to string without a memory allocation. From a573ec6a37f37f421a5fa696264e6a2a67af0944 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 3 Jan 2021 21:34:11 +0800 Subject: [PATCH 513/912] chore: update tree (#2371) Co-authored-by: Bo-Yi Wu --- tree.go | 255 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 127 insertions(+), 128 deletions(-) diff --git a/tree.go b/tree.go index 7a80af9..74e07e8 100644 --- a/tree.go +++ b/tree.go @@ -119,7 +119,6 @@ func (n *node) incrementChildPrio(pos int) int { for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- { // Swap node positions cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1] - } // Build new index char string @@ -559,8 +558,8 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]by // Use a static sized buffer on the stack in the common case. // If the path is too long, allocate a buffer on the heap instead. buf := make([]byte, 0, stackBufSize) - if l := len(path) + 1; l > stackBufSize { - buf = make([]byte, 0, l) + if length := len(path) + 1; length > stackBufSize { + buf = make([]byte, 0, length) } ciPath := n.findCaseInsensitivePathRec( @@ -600,163 +599,163 @@ walk: // Outer loop for walking the tree path = path[npLen:] ciPath = append(ciPath, n.path...) - if len(path) > 0 { - // If this node does not have a wildcard (param or catchAll) child, - // we can just look up the next child node and continue to walk down - // the tree - if !n.wildChild { - // Skip rune bytes already processed - rb = shiftNRuneBytes(rb, npLen) + if len(path) == 0 { + // We should have reached the node containing the handle. + // Check if this node has a handle registered. + if n.handlers != nil { + return ciPath + } - if rb[0] != 0 { - // Old rune not finished - idxc := rb[0] - for i, c := range []byte(n.indices) { - if c == idxc { - // continue with child node - n = n.children[i] - npLen = len(n.path) - continue walk + // No handle found. + // Try to fix the path by adding a trailing slash + if fixTrailingSlash { + for i, c := range []byte(n.indices) { + if c == '/' { + n = n.children[i] + if (len(n.path) == 1 && n.handlers != nil) || + (n.nType == catchAll && n.children[0].handlers != nil) { + return append(ciPath, '/') } + return nil } - } else { - // Process a new rune - var rv rune - - // Find rune start. - // Runes are up to 4 byte long, - // -4 would definitely be another rune. - var off int - for max := min(npLen, 3); off < max; off++ { - if i := npLen - off; utf8.RuneStart(oldPath[i]) { - // read rune from cached path - rv, _ = utf8.DecodeRuneInString(oldPath[i:]) - break - } + } + } + return nil + } + + // If this node does not have a wildcard (param or catchAll) child, + // we can just look up the next child node and continue to walk down + // the tree + if !n.wildChild { + // Skip rune bytes already processed + rb = shiftNRuneBytes(rb, npLen) + + if rb[0] != 0 { + // Old rune not finished + idxc := rb[0] + for i, c := range []byte(n.indices) { + if c == idxc { + // continue with child node + n = n.children[i] + npLen = len(n.path) + continue walk + } + } + } else { + // Process a new rune + var rv rune + + // Find rune start. + // Runes are up to 4 byte long, + // -4 would definitely be another rune. + var off int + for max := min(npLen, 3); off < max; off++ { + if i := npLen - off; utf8.RuneStart(oldPath[i]) { + // read rune from cached path + rv, _ = utf8.DecodeRuneInString(oldPath[i:]) + break } + } + + // Calculate lowercase bytes of current rune + lo := unicode.ToLower(rv) + utf8.EncodeRune(rb[:], lo) - // Calculate lowercase bytes of current rune - lo := unicode.ToLower(rv) - utf8.EncodeRune(rb[:], lo) + // Skip already processed bytes + rb = shiftNRuneBytes(rb, off) - // Skip already processed bytes + idxc := rb[0] + for i, c := range []byte(n.indices) { + // Lowercase matches + if c == idxc { + // must use a recursive approach since both the + // uppercase byte and the lowercase byte might exist + // as an index + if out := n.children[i].findCaseInsensitivePathRec( + path, ciPath, rb, fixTrailingSlash, + ); out != nil { + return out + } + break + } + } + + // If we found no match, the same for the uppercase rune, + // if it differs + if up := unicode.ToUpper(rv); up != lo { + utf8.EncodeRune(rb[:], up) rb = shiftNRuneBytes(rb, off) idxc := rb[0] for i, c := range []byte(n.indices) { - // Lowercase matches + // Uppercase matches if c == idxc { - // must use a recursive approach since both the - // uppercase byte and the lowercase byte might exist - // as an index - if out := n.children[i].findCaseInsensitivePathRec( - path, ciPath, rb, fixTrailingSlash, - ); out != nil { - return out - } - break - } - } - - // If we found no match, the same for the uppercase rune, - // if it differs - if up := unicode.ToUpper(rv); up != lo { - utf8.EncodeRune(rb[:], up) - rb = shiftNRuneBytes(rb, off) - - idxc := rb[0] - for i, c := range []byte(n.indices) { - // Uppercase matches - if c == idxc { - // Continue with child node - n = n.children[i] - npLen = len(n.path) - continue walk - } + // Continue with child node + n = n.children[i] + npLen = len(n.path) + continue walk } } } - - // Nothing found. We can recommend to redirect to the same URL - // without a trailing slash if a leaf exists for that path - if fixTrailingSlash && path == "/" && n.handlers != nil { - return ciPath - } - return nil } - n = n.children[0] - switch n.nType { - case param: - // Find param end (either '/' or path end) - end := 0 - for end < len(path) && path[end] != '/' { - end++ - } + // Nothing found. We can recommend to redirect to the same URL + // without a trailing slash if a leaf exists for that path + if fixTrailingSlash && path == "/" && n.handlers != nil { + return ciPath + } + return nil + } - // Add param value to case insensitive path - ciPath = append(ciPath, path[:end]...) + n = n.children[0] + switch n.nType { + case param: + // Find param end (either '/' or path end) + end := 0 + for end < len(path) && path[end] != '/' { + end++ + } - // We need to go deeper! - if end < len(path) { - if len(n.children) > 0 { - // Continue with child node - n = n.children[0] - npLen = len(n.path) - path = path[end:] - continue - } + // Add param value to case insensitive path + ciPath = append(ciPath, path[:end]...) - // ... but we can't - if fixTrailingSlash && len(path) == end+1 { - return ciPath - } - return nil + // We need to go deeper! + if end < len(path) { + if len(n.children) > 0 { + // Continue with child node + n = n.children[0] + npLen = len(n.path) + path = path[end:] + continue } - if n.handlers != nil { + // ... but we can't + if fixTrailingSlash && len(path) == end+1 { return ciPath } - - if fixTrailingSlash && len(n.children) == 1 { - // No handle found. Check if a handle for this path + a - // trailing slash exists - n = n.children[0] - if n.path == "/" && n.handlers != nil { - return append(ciPath, '/') - } - } - return nil - - case catchAll: - return append(ciPath, path...) - - default: - panic("invalid node type") } - } else { - // We should have reached the node containing the handle. - // Check if this node has a handle registered. + if n.handlers != nil { return ciPath } - // No handle found. - // Try to fix the path by adding a trailing slash - if fixTrailingSlash { - for i, c := range []byte(n.indices) { - if c == '/' { - n = n.children[i] - if (len(n.path) == 1 && n.handlers != nil) || - (n.nType == catchAll && n.children[0].handlers != nil) { - return append(ciPath, '/') - } - return nil - } + if fixTrailingSlash && len(n.children) == 1 { + // No handle found. Check if a handle for this path + a + // trailing slash exists + n = n.children[0] + if n.path == "/" && n.handlers != nil { + return append(ciPath, '/') } } + return nil + + case catchAll: + return append(ciPath, path...) + + default: + panic("invalid node type") } } From 4bfae4c8c8f7764cc587022ba6d9d2fd18e6c47d Mon Sep 17 00:00:00 2001 From: wuhuizuo Date: Sun, 3 Jan 2021 21:43:34 +0800 Subject: [PATCH 514/912] Support binding for slice/array obj [Rewrite] (#2302) Co-authored-by: thinkerou Co-authored-by: Bo-Yi Wu --- binding/binding.go | 3 +- binding/binding_test.go | 30 +++++++++++++- binding/default_validator.go | 52 +++++++++++++++++++---- binding/default_validator_test.go | 68 +++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 binding/default_validator_test.go diff --git a/binding/binding.go b/binding/binding.go index 5756284..5c8e235 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -51,7 +51,8 @@ type BindingUri interface { // https://github.com/go-playground/validator/tree/v8.18.2. type StructValidator interface { // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. - // If the received type is not a struct, any validation should be skipped and nil must be returned. + // If the received type is a slice|array, the validation should be performed travel on every element. + // If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned. // If the received type is a struct or pointer to a struct, the validation should be performed. // If the struct is not valid or the validation itself fails, a descriptive error should be returned. // Otherwise nil must be returned. diff --git a/binding/binding_test.go b/binding/binding_test.go index c354be9..1733617 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -35,7 +35,7 @@ type QueryTest struct { } type FooStruct struct { - Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"` + Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"` } type FooBarStruct struct { @@ -181,6 +181,20 @@ func TestBindingJSON(t *testing.T) { `{"foo": "bar"}`, `{"bar": "foo"}`) } +func TestBindingJSONSlice(t *testing.T) { + EnableDecoderDisallowUnknownFields = true + defer func() { + EnableDecoderDisallowUnknownFields = false + }() + + testBodyBindingSlice(t, JSON, "json", "/", "/", `[]`, ``) + testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{}]`) + testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": ""}]`) + testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": 123}]`) + testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"bar": 123}]`) + testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": "123456789012345678901234567890123"}]`) +} + func TestBindingJSONUseNumber(t *testing.T) { testBodyBindingUseNumber(t, JSON, "json", @@ -1181,6 +1195,20 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody assert.Error(t, err) } +func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, badBody string) { + assert.Equal(t, name, b.Name()) + + var obj1 []FooStruct + req := requestWithBody("POST", path, body) + err := b.Bind(req, &obj1) + assert.NoError(t, err) + + var obj2 []FooStruct + req = requestWithBody("POST", badPath, badBody) + err = JSON.Bind(req, &obj2) + assert.Error(t, err) +} + func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) { obj := make(map[string]string) req := requestWithBody("POST", path, body) diff --git a/binding/default_validator.go b/binding/default_validator.go index a4c1a7f..c57a120 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -5,7 +5,9 @@ package binding import ( + "fmt" "reflect" + "strings" "sync" "github.com/go-playground/validator/v10" @@ -16,22 +18,54 @@ type defaultValidator struct { validate *validator.Validate } +type sliceValidateError []error + +func (err sliceValidateError) Error() string { + var errMsgs []string + for i, e := range err { + if e == nil { + continue + } + errMsgs = append(errMsgs, fmt.Sprintf("[%d]: %s", i, e.Error())) + } + return strings.Join(errMsgs, "\n") +} + var _ StructValidator = &defaultValidator{} // ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. func (v *defaultValidator) ValidateStruct(obj interface{}) error { - value := reflect.ValueOf(obj) - valueType := value.Kind() - if valueType == reflect.Ptr { - valueType = value.Elem().Kind() + if obj == nil { + return nil } - if valueType == reflect.Struct { - v.lazyinit() - if err := v.validate.Struct(obj); err != nil { - return err + + value := reflect.ValueOf(obj) + switch value.Kind() { + case reflect.Ptr: + return v.ValidateStruct(value.Elem().Interface()) + case reflect.Struct: + return v.validateStruct(obj) + case reflect.Slice, reflect.Array: + count := value.Len() + validateRet := make(sliceValidateError, 0) + for i := 0; i < count; i++ { + if err := v.ValidateStruct(value.Index(i).Interface()); err != nil { + validateRet = append(validateRet, err) + } + } + if len(validateRet) == 0 { + return nil } + return validateRet + default: + return nil } - return nil +} + +// validateStruct receives struct type +func (v *defaultValidator) validateStruct(obj interface{}) error { + v.lazyinit() + return v.validate.Struct(obj) } // Engine returns the underlying validator engine which powers the default diff --git a/binding/default_validator_test.go b/binding/default_validator_test.go new file mode 100644 index 0000000..e9c6de4 --- /dev/null +++ b/binding/default_validator_test.go @@ -0,0 +1,68 @@ +// Copyright 2020 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "errors" + "testing" +) + +func TestSliceValidateError(t *testing.T) { + tests := []struct { + name string + err sliceValidateError + want string + }{ + {"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.err.Error(); got != tt.want { + t.Errorf("sliceValidateError.Error() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDefaultValidator(t *testing.T) { + type exampleStruct struct { + A string `binding:"max=8"` + B int `binding:"gt=0"` + } + tests := []struct { + name string + v *defaultValidator + obj interface{} + wantErr bool + }{ + {"validate nil obj", &defaultValidator{}, nil, false}, + {"validate int obj", &defaultValidator{}, 3, false}, + {"validate struct failed-1", &defaultValidator{}, exampleStruct{A: "123456789", B: 1}, true}, + {"validate struct failed-2", &defaultValidator{}, exampleStruct{A: "12345678", B: 0}, true}, + {"validate struct passed", &defaultValidator{}, exampleStruct{A: "12345678", B: 1}, false}, + {"validate *struct failed-1", &defaultValidator{}, &exampleStruct{A: "123456789", B: 1}, true}, + {"validate *struct failed-2", &defaultValidator{}, &exampleStruct{A: "12345678", B: 0}, true}, + {"validate *struct passed", &defaultValidator{}, &exampleStruct{A: "12345678", B: 1}, false}, + {"validate []struct failed-1", &defaultValidator{}, []exampleStruct{{A: "123456789", B: 1}}, true}, + {"validate []struct failed-2", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 0}}, true}, + {"validate []struct passed", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 1}}, false}, + {"validate []*struct failed-1", &defaultValidator{}, []*exampleStruct{{A: "123456789", B: 1}}, true}, + {"validate []*struct failed-2", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 0}}, true}, + {"validate []*struct passed", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 1}}, false}, + {"validate *[]struct failed-1", &defaultValidator{}, &[]exampleStruct{{A: "123456789", B: 1}}, true}, + {"validate *[]struct failed-2", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 0}}, true}, + {"validate *[]struct passed", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 1}}, false}, + {"validate *[]*struct failed-1", &defaultValidator{}, &[]*exampleStruct{{A: "123456789", B: 1}}, true}, + {"validate *[]*struct failed-2", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 0}}, true}, + {"validate *[]*struct passed", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 1}}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.v.ValidateStruct(tt.obj); (err != nil) != tt.wantErr { + t.Errorf("defaultValidator.Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From fca3f95d7cdfdef203c78f220b84118f44590512 Mon Sep 17 00:00:00 2001 From: kishor kunal raj <68464660+kishorkunal-raj@users.noreply.github.com> Date: Sun, 3 Jan 2021 20:00:22 +0530 Subject: [PATCH 515/912] Adding ppc64le architecture support on travis-ci (#2538) Co-authored-by: Bo-Yi Wu --- .travis.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0795665..9112931 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,34 @@ matrix: env: - TESTTAGS=nomsgpack - go: master + # Adding ppc64le jobs + - go: 1.11.x + arch: ppc64le + env: GO111MODULE=on + - go: 1.12.x + arch: ppc64le + env: GO111MODULE=on + - go: 1.13.x + arch: ppc64le + - go: 1.13.x + arch: ppc64le + env: + - TESTTAGS=nomsgpack + - go: 1.14.x + arch: ppc64le + - go: 1.14.x + arch: ppc64le + env: + - TESTTAGS=nomsgpack + - go: 1.15.x + arch: ppc64le + - go: 1.15.x + arch: ppc64le + env: + - TESTTAGS=nomsgpack + - go: master + arch: ppc64le + git: depth: 10 From a28cc088b5e84cb00430f4b32f2b016725a9035a Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 10 Jan 2021 23:51:02 +0800 Subject: [PATCH 516/912] Revert "Adding ppc64le architecture support on travis-ci (#2538)" (#2602) --- .travis.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9112931..0795665 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,34 +18,6 @@ matrix: env: - TESTTAGS=nomsgpack - go: master - # Adding ppc64le jobs - - go: 1.11.x - arch: ppc64le - env: GO111MODULE=on - - go: 1.12.x - arch: ppc64le - env: GO111MODULE=on - - go: 1.13.x - arch: ppc64le - - go: 1.13.x - arch: ppc64le - env: - - TESTTAGS=nomsgpack - - go: 1.14.x - arch: ppc64le - - go: 1.14.x - arch: ppc64le - env: - - TESTTAGS=nomsgpack - - go: 1.15.x - arch: ppc64le - - go: 1.15.x - arch: ppc64le - env: - - TESTTAGS=nomsgpack - - go: master - arch: ppc64le - git: depth: 10 From 4d2dad596140fa729fb5e67aed2ad9787d3a1901 Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 11 Jan 2021 09:07:45 +0800 Subject: [PATCH 517/912] test: fixed the TestUnixSocket test on windows (#2595) Co-authored-by: thinkerou --- gin_integration_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index 5f508c7..41ad987 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -14,6 +14,7 @@ import ( "net/http" "net/http/httptest" "os" + "path/filepath" "sync" "testing" "time" @@ -146,7 +147,7 @@ func TestRunWithPort(t *testing.T) { func TestUnixSocket(t *testing.T) { router := New() - unixTestSocket := "/tmp/unix_unit_test" + unixTestSocket := filepath.Join(os.TempDir(), "unix_unit_test") defer os.Remove(unixTestSocket) From e753c502dcbbab6769305871d700c770e68d1b0f Mon Sep 17 00:00:00 2001 From: Rubi <14269809+codenoid@users.noreply.github.com> Date: Mon, 11 Jan 2021 23:03:31 +0700 Subject: [PATCH 518/912] gin mode unknown: show available mode (#2567) Co-authored-by: thinkerou --- mode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mode.go b/mode.go index 11f833e..c8813af 100644 --- a/mode.go +++ b/mode.go @@ -63,7 +63,7 @@ func SetMode(value string) { case TestMode: ginMode = testCode default: - panic("gin mode unknown: " + value) + panic("gin mode unknown: " + value + " (available mode: debug release test)") } modeName = value From f4bc259de33c561fd3b0ae3e7aaa849c1d251c0b Mon Sep 17 00:00:00 2001 From: Qt Date: Tue, 12 Jan 2021 08:32:04 +0800 Subject: [PATCH 519/912] fix error gin support min Go version (#2584) Co-authored-by: thinkerou --- debug.go | 4 ++-- debug_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/debug.go b/debug.go index c66ca44..4c7cd0c 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 10 +const ginSupportMinGoVer = 12 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon. + debugPrint(`[WARNING] Now Gin requires Go 1.12+. `) } diff --git a/debug_test.go b/debug_test.go index d8cd5d1..c2272d0 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.12+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } From 46ddd4259cac975be1eb11b4f1192264f582db16 Mon Sep 17 00:00:00 2001 From: Josep Jesus Bigorra Algaba <42377845+averageflow@users.noreply.github.com> Date: Wed, 13 Jan 2021 02:06:12 +0100 Subject: [PATCH 520/912] Fixes to the graceful shutdown example (#2552) * Change error comparison to use errors.Is() and add a line of whitespace before the if statement on graceful shutdown * Change from log.Fatalf to log.Printf to ensure the graceful shutdown actually works Co-authored-by: J. J. Bigorra Co-authored-by: thinkerou --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 18b1943..0c26324 100644 --- a/README.md +++ b/README.md @@ -1793,8 +1793,8 @@ func main() { // Initializing the server in a goroutine so that // it won't block the graceful shutdown handling below go func() { - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) + if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) { + log.Printf("listen: %s\n", err) } }() @@ -1812,6 +1812,7 @@ func main() { // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown:", err) } From b01605bb5b43dbf33781970af5ad6633e5549fd1 Mon Sep 17 00:00:00 2001 From: Snawoot Date: Wed, 13 Jan 2021 03:40:37 +0200 Subject: [PATCH 521/912] basic auth: fix timing oracle (#2609) Co-authored-by: thinkerou --- auth.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth.go b/auth.go index 43ad36f..4d8a6ce 100644 --- a/auth.go +++ b/auth.go @@ -5,6 +5,7 @@ package gin import ( + "crypto/subtle" "encoding/base64" "net/http" "strconv" @@ -30,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) { return "", false } for _, pair := range a { - if pair.value == authValue { + if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 { return pair.user, true } } From e899771392ecf35de8ce10a030ed8fed2207e9cb Mon Sep 17 00:00:00 2001 From: Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> Date: Wed, 27 Jan 2021 04:58:21 +0300 Subject: [PATCH 522/912] chore: Deleted spaces (#2622) --- .travis.yml | 2 +- AUTHORS.md | 2 +- BENCHMARKS.md | 8 ++++---- CHANGELOG.md | 10 +++++----- CONTRIBUTING.md | 2 +- README.md | 52 ++++++++++++++++++++++++------------------------- 6 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0795665..8ebae71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ matrix: env: GO111MODULE=on - go: 1.13.x - go: 1.13.x - env: + env: - TESTTAGS=nomsgpack - go: 1.14.x - go: 1.14.x diff --git a/AUTHORS.md b/AUTHORS.md index dda19bc..a477611 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -156,7 +156,7 @@ People and companies, who have contributed, in alphabetical order. - Fix variadic parameter in the flexible render API - Fix Corrupted plain render - Add Pluggable View Renderer Example - + **@msemenistyi (Mykyta Semenistyi)** - update Readme.md. Add code to String method diff --git a/BENCHMARKS.md b/BENCHMARKS.md index 0f59b50..c11ee99 100644 --- a/BENCHMARKS.md +++ b/BENCHMARKS.md @@ -1,11 +1,11 @@ # Benchmark System -**VM HOST:** Travis -**Machine:** Ubuntu 16.04.6 LTS x64 -**Date:** May 04th, 2020 +**VM HOST:** Travis +**Machine:** Ubuntu 16.04.6 LTS x64 +**Date:** May 04th, 2020 **Version:** Gin v1.6.3 -**Go Version:** 1.14.2 linux/amd64 +**Go Version:** 1.14.2 linux/amd64 **Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark) **Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ac51ad..ddf30e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -215,12 +215,12 @@ ## Gin 1.1 -- [NEW] Implement QueryArray and PostArray methods -- [NEW] Refactor GetQuery and GetPostForm -- [NEW] Add contribution guide +- [NEW] Implement QueryArray and PostArray methods +- [NEW] Refactor GetQuery and GetPostForm +- [NEW] Add contribution guide - [FIX] Corrected typos in README -- [FIX] Removed additional Iota -- [FIX] Changed imports to gopkg instead of github in README (#733) +- [FIX] Removed additional Iota +- [FIX] Changed imports to gopkg instead of github in README (#733) - [FIX] Logger: skip ANSI color commands if output is not a tty ## Gin 1.0rc2 (...) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 98d758e..97daa80 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -## Contributing +## Contributing - With issues: - Use the search tool before opening a new issue. diff --git a/README.md b/README.md index 0c26324..119f945 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ import "net/http" ``` ## Quick start - + ```sh # assume the following codes in example.go file $ cat example.go @@ -588,44 +588,44 @@ func main() { ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " ``` -### Controlling Log output coloring +### Controlling Log output coloring By default, logs output on console should be colorized depending on the detected TTY. -Never colorize logs: +Never colorize logs: ```go func main() { // Disable log's color gin.DisableConsoleColor() - + // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default() - + router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) - + router.Run(":8080") } ``` -Always colorize logs: +Always colorize logs: ```go func main() { // Force log's color gin.ForceConsoleColor() - + // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default() - + router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") }) - + router.Run(":8080") } ``` @@ -667,12 +667,12 @@ func main() { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - + if json.User != "manu" || json.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) return - } - + } + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) @@ -688,12 +688,12 @@ func main() { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - + if xml.User != "manu" || xml.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) return - } - + } + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) @@ -705,12 +705,12 @@ func main() { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - + if form.User != "manu" || form.Password != "123" { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) return - } - + } + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) }) @@ -807,7 +807,7 @@ $ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09" {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} $ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10" -{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}% +{"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 registered this way. @@ -1145,7 +1145,7 @@ func main() { data := gin.H{ "foo": "bar", } - + //callback is x // Will output : x({\"foo\":\"bar\"}) c.JSONP(http.StatusOK, data) @@ -1190,21 +1190,21 @@ This feature is unavailable in Go 1.6 and lower. ```go func main() { r := gin.Default() - + // Serves unicode entities r.GET("/json", func(c *gin.Context) { c.JSON(200, gin.H{ "html": "Hello, world!", }) }) - + // Serves literal characters r.GET("/purejson", func(c *gin.Context) { c.PureJSON(200, gin.H{ "html": "Hello, world!", }) }) - + // listen and serve on 0.0.0.0:8080 r.Run(":8080") } @@ -1812,11 +1812,11 @@ func main() { // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - + if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown:", err) } - + log.Println("Server exiting") } ``` From 1bdf86b722026fd650fddfef7fe9bd8342b51b7a Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Mon, 8 Feb 2021 23:24:22 +0800 Subject: [PATCH 523/912] Remove the tedious named return value (#2620) Co-authored-by: thinkerou --- internal/bytesconv/bytesconv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv.go index fdad201..86e4c4d 100644 --- a/internal/bytesconv/bytesconv.go +++ b/internal/bytesconv/bytesconv.go @@ -9,7 +9,7 @@ import ( ) // StringToBytes converts string to byte slice without a memory allocation. -func StringToBytes(s string) (b []byte) { +func StringToBytes(s string) []byte { return *(*[]byte)(unsafe.Pointer( &struct { string From ed6f85c478ba00e5168be1f29ffcdc9a983568b8 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sat, 27 Mar 2021 09:09:44 +0800 Subject: [PATCH 524/912] build: convert to go:build directives (#2664) --- binding/binding.go | 1 + binding/binding_msgpack_test.go | 1 + binding/binding_nomsgpack.go | 1 + binding/msgpack.go | 1 + binding/msgpack_test.go | 1 + context_appengine.go | 5 +++-- errors_1.13_test.go | 1 + internal/json/json.go | 1 + internal/json/jsoniter.go | 1 + render/msgpack.go | 1 + render/render_msgpack_test.go | 1 + 11 files changed, 13 insertions(+), 2 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 5c8e235..5caeb58 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !nomsgpack // +build !nomsgpack package binding diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go index 9791a60..04d9407 100644 --- a/binding/binding_msgpack_test.go +++ b/binding/binding_msgpack_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !nomsgpack // +build !nomsgpack package binding diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index fd227b1..9afa3dc 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build nomsgpack // +build nomsgpack package binding diff --git a/binding/msgpack.go b/binding/msgpack.go index a5bc2ad..2a44299 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !nomsgpack // +build !nomsgpack package binding diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go index 296d3eb..75600ba 100644 --- a/binding/msgpack_test.go +++ b/binding/msgpack_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !nomsgpack // +build !nomsgpack package binding diff --git a/context_appengine.go b/context_appengine.go index 38c189a..d565843 100644 --- a/context_appengine.go +++ b/context_appengine.go @@ -1,9 +1,10 @@ -// +build appengine - // Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build appengine +// +build appengine + package gin func init() { diff --git a/errors_1.13_test.go b/errors_1.13_test.go index a8f9a94..5fb6057 100644 --- a/errors_1.13_test.go +++ b/errors_1.13_test.go @@ -1,3 +1,4 @@ +//go:build go1.13 // +build go1.13 package gin diff --git a/internal/json/json.go b/internal/json/json.go index 480e8bf..172aeb2 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !jsoniter // +build !jsoniter package json diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index 649a3cd..232f8dc 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build jsoniter // +build jsoniter package json diff --git a/render/msgpack.go b/render/msgpack.go index be2d45c..6ef5b6e 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !nomsgpack // +build !nomsgpack package render diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index e439ac4..8170fbe 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !nomsgpack // +build !nomsgpack package render From a331dc6a31473b7208c57ec32e14bfcec3062dbb Mon Sep 17 00:00:00 2001 From: Ni Hao Date: Sat, 27 Mar 2021 14:41:31 +0800 Subject: [PATCH 525/912] chore: remove duplicate test 'assert.Equal' (#2617) Co-authored-by: thinkerou --- routes_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/routes_test.go b/routes_test.go index 11ff71a..485f0ee 100644 --- a/routes_test.go +++ b/routes_test.go @@ -238,7 +238,6 @@ func TestRouteParamsByName(t *testing.T) { assert.True(t, ok) assert.Equal(t, name, c.Param("name")) - assert.Equal(t, name, c.Param("name")) assert.Equal(t, lastName, c.Param("last_name")) assert.Empty(t, c.Param("wtf")) @@ -272,7 +271,6 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { assert.True(t, ok) assert.Equal(t, name, c.Param("name")) - assert.Equal(t, name, c.Param("name")) assert.Equal(t, lastName, c.Param("last_name")) assert.Empty(t, c.Param("wtf")) From f3de8132c5d955784deeadb9bcf5752e9fdf0d8c Mon Sep 17 00:00:00 2001 From: Ross Wolf <31489089+rw-access@users.noreply.github.com> Date: Mon, 5 Apr 2021 20:49:08 -0600 Subject: [PATCH 526/912] Add mixed param and non-param paths (port of httprouter#329) (#2663) Co-authored-by: Bo-Yi Wu --- AUTHORS.md | 2 + CHANGELOG.md | 6 +++ README.md | 7 ++++ tree.go | 113 ++++++++++++++++++++++++++------------------------- tree_test.go | 50 +++++++++++++++++------ 5 files changed, 111 insertions(+), 67 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index a477611..c634e6b 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -190,6 +190,8 @@ People and companies, who have contributed, in alphabetical order. **@rogierlommers (Rogier Lommers)** - Add updated static serve example +**@rw-access (Ross Wolf)** +- Added support to mix exact and param routes **@se77en (Damon Zhao)** - Improve color logging diff --git a/CHANGELOG.md b/CHANGELOG.md index ddf30e1..a56d4ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Gin ChangeLog +## Gin v1.7.0 + +### ENHANCEMENTS + +* Support params and exact routes without creating conflicts [#2663](https://github.com/gin-gonic/gin/pull/2663) + ## Gin v1.6.3 ### ENHANCEMENTS diff --git a/README.md b/README.md index 119f945..eb84031 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,13 @@ func main() { c.FullPath() == "/user/:name/*action" // true }) + // This handler will add a new router for /user/groups. + // Exact routes are resolved before param routes, regardless of the order they were defined. + // Routes starting with /user/groups are never interpreted as /user/:name/... routes + router.GET("/user/groups", func(c *gin.Context) { + c.String(http.StatusOK, "The available groups are [...]", name) + }) + router.Run(":8080") } ``` diff --git a/tree.go b/tree.go index 74e07e8..ca753e6 100644 --- a/tree.go +++ b/tree.go @@ -80,6 +80,16 @@ func longestCommonPrefix(a, b string) int { return i } +// addChild will add a child node, keeping wildcards at the end +func (n *node) addChild(child *node) { + if n.wildChild && len(n.children) > 0 { + wildcardChild := n.children[len(n.children)-1] + n.children = append(n.children[:len(n.children)-1], child, wildcardChild) + } else { + n.children = append(n.children, child) + } +} + func countParams(path string) uint16 { var n uint16 s := bytesconv.StringToBytes(path) @@ -103,7 +113,7 @@ type node struct { wildChild bool nType nodeType priority uint32 - children []*node + children []*node // child nodes, at most 1 :param style node at the end of the array handlers HandlersChain fullPath string } @@ -177,36 +187,9 @@ walk: // Make new node a child of this node if i < len(path) { path = path[i:] - - if n.wildChild { - parentFullPathIndex += len(n.path) - n = n.children[0] - n.priority++ - - // Check if the wildcard matches - if len(path) >= len(n.path) && n.path == path[:len(n.path)] && - // Adding a child to a catchAll is not possible - n.nType != catchAll && - // Check for longer wildcard, e.g. :name and :names - (len(n.path) >= len(path) || path[len(n.path)] == '/') { - continue walk - } - - pathSeg := path - if n.nType != catchAll { - pathSeg = strings.SplitN(path, "/", 2)[0] - } - prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path - panic("'" + pathSeg + - "' in new path '" + fullPath + - "' conflicts with existing wildcard '" + n.path + - "' in existing prefix '" + prefix + - "'") - } - c := path[0] - // slash after param + // '/' after param if n.nType == param && c == '/' && len(n.children) == 1 { parentFullPathIndex += len(n.path) n = n.children[0] @@ -225,21 +208,47 @@ walk: } // Otherwise insert it - if c != ':' && c != '*' { + if c != ':' && c != '*' && n.nType != catchAll { // []byte for proper unicode char conversion, see #65 n.indices += bytesconv.BytesToString([]byte{c}) child := &node{ fullPath: fullPath, } - n.children = append(n.children, child) + n.addChild(child) n.incrementChildPrio(len(n.indices) - 1) n = child + } else if n.wildChild { + // inserting a wildcard node, need to check if it conflicts with the existing wildcard + n = n.children[len(n.children)-1] + n.priority++ + + // Check if the wildcard matches + if len(path) >= len(n.path) && n.path == path[:len(n.path)] && + // Adding a child to a catchAll is not possible + n.nType != catchAll && + // Check for longer wildcard, e.g. :name and :names + (len(n.path) >= len(path) || path[len(n.path)] == '/') { + continue walk + } + + // Wildcard conflict + pathSeg := path + if n.nType != catchAll { + pathSeg = strings.SplitN(pathSeg, "/", 2)[0] + } + prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path + panic("'" + pathSeg + + "' in new path '" + fullPath + + "' conflicts with existing wildcard '" + n.path + + "' in existing prefix '" + prefix + + "'") } + n.insertChild(path, fullPath, handlers) return } - // Otherwise and handle to current node + // Otherwise add handle to current node if n.handlers != nil { panic("handlers are already registered for path '" + fullPath + "'") } @@ -293,13 +302,6 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) panic("wildcards must be named with a non-empty name in path '" + fullPath + "'") } - // Check if this node has existing children which would be - // unreachable if we insert the wildcard here - if len(n.children) > 0 { - panic("wildcard segment '" + wildcard + - "' conflicts with existing children in path '" + fullPath + "'") - } - if wildcard[0] == ':' { // param if i > 0 { // Insert prefix before the current wildcard @@ -307,13 +309,13 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) path = path[i:] } - n.wildChild = true child := &node{ nType: param, path: wildcard, fullPath: fullPath, } - n.children = []*node{child} + n.addChild(child) + n.wildChild = true n = child n.priority++ @@ -326,7 +328,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) priority: 1, fullPath: fullPath, } - n.children = []*node{child} + n.addChild(child) n = child continue } @@ -360,7 +362,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) fullPath: fullPath, } - n.children = []*node{child} + n.addChild(child) n.indices = string('/') n = child n.priority++ @@ -404,18 +406,18 @@ walk: // Outer loop for walking the tree if len(path) > len(prefix) { if path[:len(prefix)] == prefix { path = path[len(prefix):] - // If this node does not have a wildcard (param or catchAll) - // child, we can just look up the next child node and continue - // to walk down the tree - if !n.wildChild { - idxc := path[0] - for i, c := range []byte(n.indices) { - if c == idxc { - n = n.children[i] - continue walk - } + + // Try all the non-wildcard children first by matching the indices + idxc := path[0] + for i, c := range []byte(n.indices) { + if c == idxc { + n = n.children[i] + continue walk } + } + // If there is no wildcard pattern, recommend a redirection + if !n.wildChild { // Nothing found. // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. @@ -423,8 +425,9 @@ walk: // Outer loop for walking the tree return } - // Handle wildcard child - n = n.children[0] + // Handle wildcard child, which is always at the end of the array + n = n.children[len(n.children)-1] + switch n.nType { case param: // Find param end (either '/' or path end) diff --git a/tree_test.go b/tree_test.go index 1cb4f55..d7c4fb0 100644 --- a/tree_test.go +++ b/tree_test.go @@ -137,6 +137,8 @@ func TestTreeWildcard(t *testing.T) { "/", "/cmd/:tool/:sub", "/cmd/:tool/", + "/cmd/whoami", + "/cmd/whoami/root/", "/src/*filepath", "/search/", "/search/:query", @@ -155,8 +157,12 @@ func TestTreeWildcard(t *testing.T) { checkRequests(t, tree, testRequests{ {"/", false, "/", nil}, - {"/cmd/test/", false, "/cmd/:tool/", Params{Param{Key: "tool", Value: "test"}}}, - {"/cmd/test", true, "", Params{Param{Key: "tool", Value: "test"}}}, + {"/cmd/test", true, "/cmd/:tool/", Params{Param{"tool", "test"}}}, + {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, + {"/cmd/whoami", false, "/cmd/whoami", nil}, + {"/cmd/whoami/", true, "/cmd/whoami", nil}, + {"/cmd/whoami/root/", false, "/cmd/whoami/root/", nil}, + {"/cmd/whoami/root", true, "/cmd/whoami/root/", nil}, {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}}, {"/src/", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/"}}}, {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}}, @@ -245,20 +251,38 @@ func testRoutes(t *testing.T, routes []testRoute) { func TestTreeWildcardConflict(t *testing.T) { routes := []testRoute{ {"/cmd/:tool/:sub", false}, - {"/cmd/vet", true}, + {"/cmd/vet", false}, + {"/foo/bar", false}, + {"/foo/:name", false}, + {"/foo/:names", true}, + {"/cmd/*path", true}, + {"/cmd/:badvar", true}, + {"/cmd/:tool/names", false}, + {"/cmd/:tool/:badsub/details", true}, {"/src/*filepath", false}, + {"/src/:file", true}, + {"/src/static.json", true}, {"/src/*filepathx", true}, {"/src/", true}, + {"/src/foo/bar", true}, {"/src1/", false}, {"/src1/*filepath", true}, {"/src2*filepath", true}, + {"/src2/*filepath", false}, {"/search/:query", false}, - {"/search/invalid", true}, + {"/search/valid", false}, {"/user_:name", false}, - {"/user_x", true}, + {"/user_x", false}, {"/user_:name", false}, {"/id:id", false}, - {"/id/:id", true}, + {"/id/:id", false}, + } + testRoutes(t, routes) +} + +func TestCatchAllAfterSlash(t *testing.T) { + routes := []testRoute{ + {"/non-leading-*catchall", true}, } testRoutes(t, routes) } @@ -266,14 +290,17 @@ func TestTreeWildcardConflict(t *testing.T) { func TestTreeChildConflict(t *testing.T) { routes := []testRoute{ {"/cmd/vet", false}, - {"/cmd/:tool/:sub", true}, + {"/cmd/:tool", false}, + {"/cmd/:tool/:sub", false}, + {"/cmd/:tool/misc", false}, + {"/cmd/:tool/:othersub", true}, {"/src/AUTHORS", false}, {"/src/*filepath", true}, {"/user_x", false}, - {"/user_:name", true}, + {"/user_:name", false}, {"/id/:id", false}, - {"/id:id", true}, - {"/:id", true}, + {"/id:id", false}, + {"/:id", false}, {"/*filepath", true}, } testRoutes(t, routes) @@ -688,8 +715,7 @@ func TestTreeWildcardConflictEx(t *testing.T) { {"/who/are/foo", "/foo", `/who/are/\*you`, `/\*you`}, {"/who/are/foo/", "/foo/", `/who/are/\*you`, `/\*you`}, {"/who/are/foo/bar", "/foo/bar", `/who/are/\*you`, `/\*you`}, - {"/conxxx", "xxx", `/con:tact`, `:tact`}, - {"/conooo/xxx", "ooo", `/con:tact`, `:tact`}, + {"/con:nection", ":nection", `/con:tact`, `:tact`}, } for _, conflict := range conflicts { From bfc8ca285eb46dad60e037d57c545cd260636711 Mon Sep 17 00:00:00 2001 From: Manu MA Date: Tue, 6 Apr 2021 05:37:25 +0200 Subject: [PATCH 527/912] feat(engine): add trustedproxies and remoteIP (#2632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Søren L. Hansen Co-authored-by: Bo-Yi Wu Co-authored-by: thinkerou Co-authored-by: Javier Provecho Fernandez --- README.md | 33 ++++++++++ context.go | 82 +++++++++++++++++++----- context_test.go | 84 +++++++++++++++++++++++-- gin.go | 73 +++++++++++++++++++++- gin_integration_test.go | 7 +++ gin_test.go | 134 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 391 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index eb84031..d4772d7 100644 --- a/README.md +++ b/README.md @@ -2124,6 +2124,39 @@ func main() { } ``` +## Don't trust all proxies + +Gin lets you specify which headers to hold the real client IP (if any), +as well as specifying which proxies (or direct clients) you trust to +specify one of these headers. + +The `TrustedProxies` slice on your `gin.Engine` specifes network addresses or +network CIDRs from where clients which their request headers related to client +IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or +IPv6 CIDRs. + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + router.TrustedProxies = []string{"192.168.1.2"} + + router.GET("/", func(c *gin.Context) { + // If the client is 192.168.1.2, use the X-Forwarded-For + // header to deduce the original client IP from the trust- + // worthy parts of that header. + // Otherwise, simply return the direct client IP + fmt.Printf("ClientIP: %s\n", c.ClientIP()) + }) + router.Run() +} +``` ## Testing diff --git a/context.go b/context.go index 3d6b56d..1ba0fa2 100644 --- a/context.go +++ b/context.go @@ -725,32 +725,82 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e return bb.BindBody(body, obj) } -// ClientIP implements a best effort algorithm to return the real client IP, it parses -// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy. -// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP. +// ClientIP implements a best effort algorithm to return the real client IP. +// It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. +// If it's it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). +// If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy, +// the remote IP (coming form Request.RemoteAddr) is returned. func (c *Context) ClientIP() string { - if c.engine.ForwardedByClientIP { - clientIP := c.requestHeader("X-Forwarded-For") - clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0]) - if clientIP == "" { - clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) + if c.engine.AppEngine { + if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { + return addr } - if clientIP != "" { - return clientIP + } + + remoteIP, trusted := c.RemoteIP() + if remoteIP == nil { + return "" + } + + if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil { + for _, headerName := range c.engine.RemoteIPHeaders { + ip, valid := validateHeader(c.requestHeader(headerName)) + if valid { + return ip + } } } + return remoteIP.String() +} - if c.engine.AppEngine { - if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { - return addr +// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port). +// It also checks if the remoteIP is a trusted proxy or not. +// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks +// defined in Engine.TrustedProxies +func (c *Context) RemoteIP() (net.IP, bool) { + ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)) + if err != nil { + return nil, false + } + remoteIP := net.ParseIP(ip) + if remoteIP == nil { + return nil, false + } + + trustedCIDRs, _ := c.engine.prepareTrustedCIDRs() + c.engine.trustedCIDRs = trustedCIDRs + if c.engine.trustedCIDRs != nil { + for _, cidr := range c.engine.trustedCIDRs { + if cidr.Contains(remoteIP) { + return remoteIP, true + } } } - if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil { - return ip + return remoteIP, false +} + +func validateHeader(header string) (clientIP string, valid bool) { + if header == "" { + return "", false } + items := strings.Split(header, ",") + for i, ipStr := range items { + ipStr = strings.TrimSpace(ipStr) + ip := net.ParseIP(ipStr) + if ip == nil { + return "", false + } - return "" + // We need to return the first IP in the list, but, + // we should not early return since we need to validate that + // the rest of the header is syntactically valid + if i == 0 { + clientIP = ipStr + valid = true + } + } + return } // ContentType returns the Content-Type header of the request. diff --git a/context_test.go b/context_test.go index 8e1e3b5..8fe4761 100644 --- a/context_test.go +++ b/context_test.go @@ -1392,11 +1392,10 @@ func TestContextClientIP(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ") - c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30") - c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50") - c.Request.RemoteAddr = " 40.40.40.40:42123 " + resetContextForClientIPTests(c) + // Legacy tests (validating that the defaults don't break the + // (insecure!) old behaviour) assert.Equal(t, "20.20.20.20", c.ClientIP()) c.Request.Header.Del("X-Forwarded-For") @@ -1416,6 +1415,74 @@ func TestContextClientIP(t *testing.T) { // no port c.Request.RemoteAddr = "50.50.50.50" assert.Empty(t, c.ClientIP()) + + // Tests exercising the TrustedProxies functionality + resetContextForClientIPTests(c) + + // No trusted proxies + c.engine.TrustedProxies = []string{} + c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"} + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Last proxy is trusted, but the RemoteAddr is not + c.engine.TrustedProxies = []string{"30.30.30.30"} + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Only trust RemoteAddr + c.engine.TrustedProxies = []string{"40.40.40.40"} + assert.Equal(t, "20.20.20.20", c.ClientIP()) + + // All steps are trusted + c.engine.TrustedProxies = []string{"40.40.40.40", "30.30.30.30", "20.20.20.20"} + assert.Equal(t, "20.20.20.20", c.ClientIP()) + + // Use CIDR + c.engine.TrustedProxies = []string{"40.40.25.25/16", "30.30.30.30"} + assert.Equal(t, "20.20.20.20", c.ClientIP()) + + // Use hostname that resolves to all the proxies + c.engine.TrustedProxies = []string{"foo"} + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Use hostname that returns an error + c.engine.TrustedProxies = []string{"bar"} + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // X-Forwarded-For has a non-IP element + c.engine.TrustedProxies = []string{"40.40.40.40"} + c.Request.Header.Set("X-Forwarded-For", " blah ") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Result from LookupHost has non-IP element. This should never + // happen, but we should test it to make sure we handle it + // gracefully. + c.engine.TrustedProxies = []string{"baz"} + c.Request.Header.Set("X-Forwarded-For", " 30.30.30.30 ") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + c.engine.TrustedProxies = []string{"40.40.40.40"} + c.Request.Header.Del("X-Forwarded-For") + c.engine.RemoteIPHeaders = []string{"X-Forwarded-For", "X-Real-IP"} + assert.Equal(t, "10.10.10.10", c.ClientIP()) + + c.engine.RemoteIPHeaders = []string{} + c.engine.AppEngine = true + assert.Equal(t, "50.50.50.50", c.ClientIP()) + + c.Request.Header.Del("X-Appengine-Remote-Addr") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // no port + c.Request.RemoteAddr = "50.50.50.50" + assert.Empty(t, c.ClientIP()) +} + +func resetContextForClientIPTests(c *Context) { + c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ") + c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30") + c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50") + c.Request.RemoteAddr = " 40.40.40.40:42123 " + c.engine.AppEngine = false } func TestContextContentType(t *testing.T) { @@ -1960,3 +2027,12 @@ func TestContextWithKeysMutex(t *testing.T) { assert.Nil(t, value) assert.False(t, err) } + +func TestRemoteIPFail(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.RemoteAddr = "[:::]:80" + ip, trust := c.RemoteIP() + assert.Nil(t, ip) + assert.False(t, trust) +} diff --git a/gin.go b/gin.go index 1e12617..03a0e12 100644 --- a/gin.go +++ b/gin.go @@ -11,6 +11,7 @@ import ( "net/http" "os" "path" + "strings" "sync" "github.com/gin-gonic/gin/internal/bytesconv" @@ -81,9 +82,26 @@ type Engine struct { // If no other Method is allowed, the request is delegated to the NotFound // handler. HandleMethodNotAllowed bool - ForwardedByClientIP bool - // #726 #755 If enabled, it will thrust some headers starting with + // If enabled, client IP will be parsed from the request's headers that + // match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was + // fetched, it falls back to the IP obtained from + // `(*gin.Context).Request.RemoteAddr`. + ForwardedByClientIP bool + + // List of headers used to obtain the client IP when + // `(*gin.Engine).ForwardedByClientIP` is `true` and + // `(*gin.Context).Request.RemoteAddr` is matched by at least one of the + // network origins of `(*gin.Engine).TrustedProxies`. + RemoteIPHeaders []string + + // List of network origins (IPv4 addresses, IPv4 CIDRs, IPv6 addresses or + // IPv6 CIDRs) from which to trust request's headers that contain + // alternative client IP when `(*gin.Engine).ForwardedByClientIP` is + // `true`. + TrustedProxies []string + + // #726 #755 If enabled, it will trust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool @@ -114,6 +132,7 @@ type Engine struct { pool sync.Pool trees methodTrees maxParams uint16 + trustedCIDRs []*net.IPNet } var _ IRouter = &Engine{} @@ -139,6 +158,8 @@ func New() *Engine { RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, + RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"}, + TrustedProxies: []string{"0.0.0.0/0"}, AppEngine: defaultAppEngine, UseRawPath: false, RemoveExtraSlash: false, @@ -305,12 +326,60 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() + trustedCIDRs, err := engine.prepareTrustedCIDRs() + if err != nil { + return err + } + engine.trustedCIDRs = trustedCIDRs address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine) return } +func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) { + if engine.TrustedProxies == nil { + return nil, nil + } + + cidr := make([]*net.IPNet, 0, len(engine.TrustedProxies)) + for _, trustedProxy := range engine.TrustedProxies { + if !strings.Contains(trustedProxy, "/") { + ip := parseIP(trustedProxy) + if ip == nil { + return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy} + } + + switch len(ip) { + case net.IPv4len: + trustedProxy += "/32" + case net.IPv6len: + trustedProxy += "/128" + } + } + _, cidrNet, err := net.ParseCIDR(trustedProxy) + if err != nil { + return cidr, err + } + cidr = append(cidr, cidrNet) + } + return cidr, nil +} + +// parseIP parse a string representation of an IP and returns a net.IP with the +// minimum byte representation or nil if input is invalid. +func parseIP(ip string) net.IP { + parsedIP := net.ParseIP(ip) + + if ipv4 := parsedIP.To4(); ipv4 != nil { + // return ip in a 4-byte representation + return ipv4 + } + + // return ip in a 16-byte representation or nil + return parsedIP +} + // 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. diff --git a/gin_integration_test.go b/gin_integration_test.go index 41ad987..fd97265 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -55,6 +55,13 @@ func TestRunEmpty(t *testing.T) { testRequest(t, "http://localhost:8080/example") } +func TestTrustedCIDRsForRun(t *testing.T) { + os.Setenv("PORT", "") + router := New() + router.TrustedProxies = []string{"hello/world"} + assert.Error(t, router.Run(":8080")) +} + func TestRunTLS(t *testing.T) { router := New() go func() { diff --git a/gin_test.go b/gin_test.go index 11bdd79..678d95f 100644 --- a/gin_test.go +++ b/gin_test.go @@ -9,6 +9,7 @@ import ( "fmt" "html/template" "io/ioutil" + "net" "net/http" "net/http/httptest" "reflect" @@ -532,6 +533,139 @@ func TestEngineHandleContextManyReEntries(t *testing.T) { assert.Equal(t, int64(expectValue), middlewareCounter) } +func TestPrepareTrustedCIRDsWith(t *testing.T) { + r := New() + + // valid ipv4 cidr + { + expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")} + r.TrustedProxies = []string{"0.0.0.0/0"} + + trustedCIDRs, err := r.prepareTrustedCIDRs() + + assert.NoError(t, err) + assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + } + + // invalid ipv4 cidr + { + r.TrustedProxies = []string{"192.168.1.33/33"} + + _, err := r.prepareTrustedCIDRs() + + assert.Error(t, err) + } + + // valid ipv4 address + { + expectedTrustedCIDRs := []*net.IPNet{parseCIDR("192.168.1.33/32")} + r.TrustedProxies = []string{"192.168.1.33"} + + trustedCIDRs, err := r.prepareTrustedCIDRs() + + assert.NoError(t, err) + assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + } + + // invalid ipv4 address + { + r.TrustedProxies = []string{"192.168.1.256"} + + _, err := r.prepareTrustedCIDRs() + + assert.Error(t, err) + } + + // valid ipv6 address + { + expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")} + r.TrustedProxies = []string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"} + + trustedCIDRs, err := r.prepareTrustedCIDRs() + + assert.NoError(t, err) + assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + } + + // invalid ipv6 address + { + r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"} + + _, err := r.prepareTrustedCIDRs() + + assert.Error(t, err) + } + + // valid ipv6 cidr + { + expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")} + r.TrustedProxies = []string{"::/0"} + + trustedCIDRs, err := r.prepareTrustedCIDRs() + + assert.NoError(t, err) + assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + } + + // invalid ipv6 cidr + { + r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"} + + _, err := r.prepareTrustedCIDRs() + + assert.Error(t, err) + } + + // valid combination + { + expectedTrustedCIDRs := []*net.IPNet{ + parseCIDR("::/0"), + parseCIDR("192.168.0.0/16"), + parseCIDR("172.16.0.1/32"), + } + r.TrustedProxies = []string{ + "::/0", + "192.168.0.0/16", + "172.16.0.1", + } + + trustedCIDRs, err := r.prepareTrustedCIDRs() + + assert.NoError(t, err) + assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + } + + // invalid combination + { + r.TrustedProxies = []string{ + "::/0", + "192.168.0.0/16", + "172.16.0.256", + } + _, err := r.prepareTrustedCIDRs() + + assert.Error(t, err) + } + + // nil value + { + r.TrustedProxies = nil + trustedCIDRs, err := r.prepareTrustedCIDRs() + + assert.Nil(t, trustedCIDRs) + assert.Nil(t, err) + } + +} + +func parseCIDR(cidr string) *net.IPNet { + _, parsedCIDR, err := net.ParseCIDR(cidr) + if err != nil { + fmt.Println(err) + } + return parsedCIDR +} + func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { for _, gotRoute := range gotRoutes { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { From d496f64540b6707602de50ab57aeea8ff4080b74 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 8 Apr 2021 15:47:41 +0800 Subject: [PATCH 528/912] bump to v1.7.0 version (#2672) --- CHANGELOG.md | 29 ++++++++++++++++++++++++++++- version.go | 2 +- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a56d4ff..44d6251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,36 @@ ## Gin v1.7.0 +### BUGFIXES + +* fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600)) +* fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528)) +* fix(tree): reassign fullpath when register new node ([#2366](https://github.com/gin-gonic/gin/pull/2366)) + ### ENHANCEMENTS -* Support params and exact routes without creating conflicts [#2663](https://github.com/gin-gonic/gin/pull/2663) +* Support params and exact routes without creating conflicts ([#2663](https://github.com/gin-gonic/gin/pull/2663)) +* chore: improve render string performance ([#2365](https://github.com/gin-gonic/gin/pull/2365)) +* Sync route tree to httprouter latest code ([#2368](https://github.com/gin-gonic/gin/pull/2368)) +* chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa ([#2375](https://github.com/gin-gonic/gin/pull/2375)) +* chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378)) +* Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387)) +* update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321)) +* remove a unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391)) +* Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389)) +* Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322)) +* binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450)) +* Prevent panic in Context.GetQuery() when there is no Request ([#2412](https://github.com/gin-gonic/gin/pull/2412)) +* Add GetUint and GetUint64 method on gin.context ([#2487](https://github.com/gin-gonic/gin/pull/2487)) +* update content-disposition header to MIME-style ([#2512](https://github.com/gin-gonic/gin/pull/2512)) +* reduce allocs and improve the render `WriteString` ([#2508](https://github.com/gin-gonic/gin/pull/2508)) +* implement ".Unwrap() error" on Error type ([#2525](https://github.com/gin-gonic/gin/pull/2525)) ([#2526](https://github.com/gin-gonic/gin/pull/2526)) +* Allow bind with a map[string]string ([#2484](https://github.com/gin-gonic/gin/pull/2484)) +* chore: update tree ([#2371](https://github.com/gin-gonic/gin/pull/2371)) +* Support binding for slice/array obj [Rewrite] ([#2302](https://github.com/gin-gonic/gin/pull/2302)) +* basic auth: fix timing oracle ([#2609](https://github.com/gin-gonic/gin/pull/2609)) +* Add mixed param and non-param paths (port of httprouter[#329](https://github.com/gin-gonic/gin/pull/329)) ([#2663](https://github.com/gin-gonic/gin/pull/2663)) +* feat(engine): add trustedproxies and remoteIP ([#2632](https://github.com/gin-gonic/gin/pull/2632)) ## Gin v1.6.3 diff --git a/version.go b/version.go index 3e9687d..95b4ed1 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.6.3" +const Version = "v1.7.0" From 03e5e05ae089bc989f1ca41841f05504d29e3fd9 Mon Sep 17 00:00:00 2001 From: Xudong Cai Date: Fri, 9 Apr 2021 00:27:34 +0800 Subject: [PATCH 529/912] fix: data race with trustedCIDRs (#2674) (#2675) Co-authored-by: Bo-Yi Wu --- context.go | 2 -- context_test.go | 16 +++++++++++++++- logger_test.go | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 1ba0fa2..dc03c35 100644 --- a/context.go +++ b/context.go @@ -767,8 +767,6 @@ func (c *Context) RemoteIP() (net.IP, bool) { return nil, false } - trustedCIDRs, _ := c.engine.prepareTrustedCIDRs() - c.engine.trustedCIDRs = trustedCIDRs if c.engine.trustedCIDRs != nil { for _, cidr := range c.engine.trustedCIDRs { if cidr.Contains(remoteIP) { diff --git a/context_test.go b/context_test.go index 8fe4761..cf3f0be 100644 --- a/context_test.go +++ b/context_test.go @@ -1388,10 +1388,14 @@ func TestContextAbortWithError(t *testing.T) { assert.True(t, c.IsAborted()) } +func resetTrustedCIDRs(c *Context) { + c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs() +} + func TestContextClientIP(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) - + resetTrustedCIDRs(c) resetContextForClientIPTests(c) // Legacy tests (validating that the defaults don't break the @@ -1421,35 +1425,43 @@ func TestContextClientIP(t *testing.T) { // No trusted proxies c.engine.TrustedProxies = []string{} + resetTrustedCIDRs(c) c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"} assert.Equal(t, "40.40.40.40", c.ClientIP()) // Last proxy is trusted, but the RemoteAddr is not c.engine.TrustedProxies = []string{"30.30.30.30"} + resetTrustedCIDRs(c) assert.Equal(t, "40.40.40.40", c.ClientIP()) // Only trust RemoteAddr c.engine.TrustedProxies = []string{"40.40.40.40"} + resetTrustedCIDRs(c) assert.Equal(t, "20.20.20.20", c.ClientIP()) // All steps are trusted c.engine.TrustedProxies = []string{"40.40.40.40", "30.30.30.30", "20.20.20.20"} + resetTrustedCIDRs(c) assert.Equal(t, "20.20.20.20", c.ClientIP()) // Use CIDR c.engine.TrustedProxies = []string{"40.40.25.25/16", "30.30.30.30"} + resetTrustedCIDRs(c) assert.Equal(t, "20.20.20.20", c.ClientIP()) // Use hostname that resolves to all the proxies c.engine.TrustedProxies = []string{"foo"} + resetTrustedCIDRs(c) assert.Equal(t, "40.40.40.40", c.ClientIP()) // Use hostname that returns an error c.engine.TrustedProxies = []string{"bar"} + resetTrustedCIDRs(c) assert.Equal(t, "40.40.40.40", c.ClientIP()) // X-Forwarded-For has a non-IP element c.engine.TrustedProxies = []string{"40.40.40.40"} + resetTrustedCIDRs(c) c.Request.Header.Set("X-Forwarded-For", " blah ") assert.Equal(t, "40.40.40.40", c.ClientIP()) @@ -1457,10 +1469,12 @@ func TestContextClientIP(t *testing.T) { // happen, but we should test it to make sure we handle it // gracefully. c.engine.TrustedProxies = []string{"baz"} + resetTrustedCIDRs(c) c.Request.Header.Set("X-Forwarded-For", " 30.30.30.30 ") assert.Equal(t, "40.40.40.40", c.ClientIP()) c.engine.TrustedProxies = []string{"40.40.40.40"} + resetTrustedCIDRs(c) c.Request.Header.Del("X-Forwarded-For") c.engine.RemoteIPHeaders = []string{"X-Forwarded-For", "X-Real-IP"} assert.Equal(t, "10.10.10.10", c.ClientIP()) diff --git a/logger_test.go b/logger_test.go index 0d40666..80961ce 100644 --- a/logger_test.go +++ b/logger_test.go @@ -185,6 +185,8 @@ func TestLoggerWithConfigFormatting(t *testing.T) { buffer := new(bytes.Buffer) router := New() + router.engine.trustedCIDRs, _ = router.engine.prepareTrustedCIDRs() + router.Use(LoggerWithConfig(LoggerConfig{ Output: buffer, Formatter: func(param LogFormatterParams) string { From 51c7d001e086ef3eda9cb08d5bbb63c605ec06af Mon Sep 17 00:00:00 2001 From: thinkerou Date: Fri, 9 Apr 2021 07:38:13 +0800 Subject: [PATCH 530/912] bump to v1.7.1 (#2678) --- CHANGELOG.md | 6 ++++++ version.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d6251..dc2c2f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Gin ChangeLog +## Gin v1.7.1 + +### BUGFIXES + +* fix: data race with trustedCIDRs from [#2674](https://github.com/gin-gonic/gin/issues/2674)([#2675](https://github.com/gin-gonic/gin/pull/2675)) + ## Gin v1.7.0 ### BUGFIXES diff --git a/version.go b/version.go index 95b4ed1..3647461 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.7.0" +const Version = "v1.7.1" From ee4de846a894e9049321e809d69f4343f62d2862 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 12 Apr 2021 00:29:34 +0800 Subject: [PATCH 531/912] Remove go1.12 support (#2679) * Revert "Adding ppc64le architecture support on travis-ci (#2538)" This reverts commit fca3f95d7cdfdef203c78f220b84118f44590512. * not support go1.12 * fix * Update errors_test.go * Update debug.go --- .travis.yml | 2 -- README.md | 2 +- debug.go | 4 ++-- debug_test.go | 2 +- errors_1.13_test.go | 34 ---------------------------------- errors_test.go | 22 ++++++++++++++++++++++ 6 files changed, 26 insertions(+), 40 deletions(-) delete mode 100644 errors_1.13_test.go diff --git a/.travis.yml b/.travis.yml index 8ebae71..bcc2141 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: go matrix: fast_finish: true include: - - go: 1.12.x - env: GO111MODULE=on - go: 1.13.x - go: 1.13.x env: diff --git a/README.md b/README.md index d4772d7..e5e3b46 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. The first need [Go](https://golang.org/) installed (**version 1.12+ is required**), then you can use the below Go command to install Gin. +1. The first need [Go](https://golang.org/) installed (**version 1.13+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin diff --git a/debug.go b/debug.go index 4c7cd0c..9bacc68 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 12 +const ginSupportMinGoVer = 13 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.12+. + debugPrint(`[WARNING] Now Gin requires Go 1.13+. `) } diff --git a/debug_test.go b/debug_test.go index c2272d0..0550999 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.12+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.13+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/errors_1.13_test.go b/errors_1.13_test.go deleted file mode 100644 index 5fb6057..0000000 --- a/errors_1.13_test.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -package gin - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -type TestErr string - -func (e TestErr) Error() string { return string(e) } - -// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()". -// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13, -// hence the "// +build go1.13" directive at the beginning of this file. -func TestErrorUnwrap(t *testing.T) { - innerErr := TestErr("somme error") - - // 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr - err := fmt.Errorf("wrapped: %w", &Error{ - Err: innerErr, - Type: ErrorTypeAny, - }) - - // check that 'errors.Is()' and 'errors.As()' behave as expected : - assert.True(t, errors.Is(err, innerErr)) - var testErr TestErr - assert.True(t, errors.As(err, &testErr)) -} diff --git a/errors_test.go b/errors_test.go index 6aae1c1..ee95ab3 100644 --- a/errors_test.go +++ b/errors_test.go @@ -6,6 +6,7 @@ package gin import ( "errors" + "fmt" "testing" "github.com/gin-gonic/gin/internal/json" @@ -104,3 +105,24 @@ Error #03: third assert.Nil(t, errs.JSON()) assert.Empty(t, errs.String()) } + +type TestErr string + +func (e TestErr) Error() string { return string(e) } + +// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()". +// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13. +func TestErrorUnwrap(t *testing.T) { + innerErr := TestErr("somme error") + + // 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr + err := fmt.Errorf("wrapped: %w", &Error{ + Err: innerErr, + Type: ErrorTypeAny, + }) + + // check that 'errors.Is()' and 'errors.As()' behave as expected : + assert.True(t, errors.Is(err, innerErr)) + var testErr TestErr + assert.True(t, errors.As(err, &testErr)) +} From 77649bcfee1a3eef1cc79839457ed5369916edb4 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 21 Apr 2021 07:38:54 +0800 Subject: [PATCH 532/912] support Go v1.16 version (#2638) --- .travis.yml | 4 ++++ context_test.go | 8 ++++++-- routes_test.go | 4 +++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index bcc2141..8166231 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,10 @@ matrix: - go: 1.15.x env: - TESTTAGS=nomsgpack + - go: 1.16.x + - go: 1.16.x + env: + - TESTTAGS=nomsgpack - go: master git: diff --git a/context_test.go b/context_test.go index cf3f0be..8e9f726 100644 --- a/context_test.go +++ b/context_test.go @@ -1018,7 +1018,9 @@ func TestContextRenderFile(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") - assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, + // else, Content-Type='text/x-go; charset=utf-8' + assert.NotEqual(t, "", w.Header().Get("Content-Type")) } func TestContextRenderFileFromFS(t *testing.T) { @@ -1030,7 +1032,9 @@ func TestContextRenderFileFromFS(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") - assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, + // else, Content-Type='text/x-go; charset=utf-8' + assert.NotEqual(t, "", w.Header().Get("Content-Type")) assert.Equal(t, "/some/path", c.Request.URL.Path) } diff --git a/routes_test.go b/routes_test.go index 485f0ee..036fa1c 100644 --- a/routes_test.go +++ b/routes_test.go @@ -360,7 +360,9 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") - assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, + // else, Content-Type='text/x-go; charset=utf-8' + assert.NotEqual(t, "", w.Header().Get("Content-Type")) assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) From 7313b8fddc61fa4df9788abfda4fab53a83dfead Mon Sep 17 00:00:00 2001 From: y-yagi Date: Wed, 21 Apr 2021 08:55:08 +0900 Subject: [PATCH 533/912] Use `Header()` instead of deprecated `HeaderMap` (#2694) Co-authored-by: Bo-Yi Wu --- context_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context_test.go b/context_test.go index 8e9f726..993c632 100644 --- a/context_test.go +++ b/context_test.go @@ -1048,7 +1048,7 @@ func TestContextRenderAttachment(t *testing.T) { assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") - assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.HeaderMap.Get("Content-Disposition")) + assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition")) } // TestContextRenderYAML tests that the response is serialized as YAML From f1da692fbdf67736b371eada8997b25f91de5c30 Mon Sep 17 00:00:00 2001 From: heige Date: Wed, 21 Apr 2021 08:24:55 +0800 Subject: [PATCH 534/912] RouterGroup.Handle regular match optimization of http method (#2685) Co-authored-by: thinkerou Co-authored-by: Bo-Yi Wu --- routergroup.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 15d9930..6f14bf5 100644 --- a/routergroup.go +++ b/routergroup.go @@ -11,6 +11,11 @@ import ( "strings" ) +var ( + // reg match english letters for http method name + regEnLetter = regexp.MustCompile("^[A-Z]+$") +) + // IRouter defines all router handle interface includes single and group router. type IRouter interface { IRoutes @@ -87,7 +92,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl // frequently used, non-standardized or custom methods (e.g. for internal // communication with a proxy). func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes { - if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil { + if matched := regEnLetter.MatchString(httpMethod); !matched { panic("http method " + httpMethod + " is not valid") } return group.handle(httpMethod, relativePath, handlers) From c0418c48e45eabbeceabdaef1b70e4221b980cdd Mon Sep 17 00:00:00 2001 From: zzjin Date: Wed, 21 Apr 2021 08:45:49 +0800 Subject: [PATCH 535/912] Add support go-json, another drop-in json replacement. (#2680) Co-authored-by: Bo-Yi Wu Co-authored-by: thinkerou --- README.md | 12 +++++++++--- go.sum | 2 ++ internal/json/go_json.go | 23 +++++++++++++++++++++++ internal/json/json.go | 4 ++-- 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 internal/json/go_json.go diff --git a/README.md b/README.md index e5e3b46..2e953f5 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Quick start](#quick-start) - [Benchmarks](#benchmarks) - [Gin v1. stable](#gin-v1-stable) - - [Build with jsoniter](#build-with-jsoniter) + - [Build with jsoniter/go-json](#build-with-json-replacement) - [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) @@ -182,13 +182,18 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr - [x] Battle tested. - [x] API frozen, new releases will not break your code. -## Build with [jsoniter](https://github.com/json-iterator/go) +## Build with json replacement -Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. +Gin uses `encoding/json` as default json package but you can change it by build from other tags. +[jsoniter](https://github.com/json-iterator/go) ```sh $ go build -tags=jsoniter . ``` +[go-json](https://github.com/goccy/go-json) +```sh +$ go build -tags=go_json . +``` ## API Examples @@ -2215,3 +2220,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. + diff --git a/go.sum b/go.sum index a64b331..ac92ada 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/goccy/go-json v0.4.11 h1:92nyX606ZN/cUFwctfxwDWm8YWSA38Zlv9s7taFeLyo= +github.com/goccy/go-json v0.4.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/internal/json/go_json.go b/internal/json/go_json.go new file mode 100644 index 0000000..da96057 --- /dev/null +++ b/internal/json/go_json.go @@ -0,0 +1,23 @@ +// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go_json +// +build go_json + +package json + +import json "github.com/goccy/go-json" + +var ( + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal + // MarshalIndent is exported by gin/json package. + MarshalIndent = json.MarshalIndent + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder +) diff --git a/internal/json/json.go b/internal/json/json.go index 172aeb2..75b6022 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build !jsoniter -// +build !jsoniter +//go:build !jsoniter && !go_json +// +build !jsoniter,!go_json package json From 215c9ce231e55e369e072d402fb9dbd0ec01aa1a Mon Sep 17 00:00:00 2001 From: Qt Date: Wed, 28 Apr 2021 18:39:09 +0800 Subject: [PATCH 536/912] use errors.New to replace fmt.Errorf will much better (#2707) --- binding/json.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binding/json.go b/binding/json.go index d62e070..45aaa49 100644 --- a/binding/json.go +++ b/binding/json.go @@ -6,7 +6,7 @@ package binding import ( "bytes" - "fmt" + "errors" "io" "net/http" @@ -32,7 +32,7 @@ func (jsonBinding) Name() string { func (jsonBinding) Bind(req *http.Request, obj interface{}) error { if req == nil || req.Body == nil { - return fmt.Errorf("invalid request") + return errors.New("invalid request") } return decodeJSON(req.Body, obj) } From 1acb459c10c51c708d57ccc23c382418fed641b2 Mon Sep 17 00:00:00 2001 From: y-yagi Date: Sat, 1 May 2021 14:57:22 +0900 Subject: [PATCH 537/912] Fix example code of `Bind Uri` (#2710) Need to pass a string to `gin.H` to show a message correctly. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e953f5..566e5cc 100644 --- a/README.md +++ b/README.md @@ -930,7 +930,7 @@ func main() { route.GET("/:name/:id", func(c *gin.Context) { var person Person if err := c.ShouldBindUri(&person); err != nil { - c.JSON(400, gin.H{"msg": err}) + c.JSON(400, gin.H{"msg": err.Error()}) return } c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) From 5452a1d3ef0982ffea95fb9e88e5425b0928c431 Mon Sep 17 00:00:00 2001 From: y-yagi Date: Sat, 1 May 2021 15:13:50 +0900 Subject: [PATCH 538/912] Add note about `nomsgpack` tag to the readme (#2703) Co-authored-by: Bo-Yi Wu --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 566e5cc..6f9178c 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Benchmarks](#benchmarks) - [Gin v1. stable](#gin-v1-stable) - [Build with jsoniter/go-json](#build-with-json-replacement) + - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature) - [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) @@ -195,6 +196,16 @@ $ go build -tags=jsoniter . $ go build -tags=go_json . ``` +## Build without `MsgPack` rendering feature + +Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag. + +```sh +$ go build -tags=nomsgpack . +``` + +This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852). + ## API Examples You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). From 4fe5f3e4b4fe62c057ec22caee4908beeef5f59c Mon Sep 17 00:00:00 2001 From: y-yagi Date: Tue, 4 May 2021 23:38:14 +0900 Subject: [PATCH 539/912] Use `Duration.Truncate` for truncating precision (#2711) `Duration.Truncate` was added in Go 1.9 and Gin required Go version 1.13+ now. So we can use `Duration.Truncate`. Co-authored-by: Bo-Yi Wu --- logger.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/logger.go b/logger.go index d361b74..22138a8 100644 --- a/logger.go +++ b/logger.go @@ -138,8 +138,7 @@ var defaultLogFormatter = func(param LogFormatterParams) string { } if param.Latency > time.Minute { - // Truncate in a golang < 1.8 safe way - param.Latency = param.Latency - param.Latency%time.Second + param.Latency = param.Latency.Truncate(time.Second) } return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), From 2921582d11d517dff4cf0226bfbcd8bc7c63d536 Mon Sep 17 00:00:00 2001 From: Yue Yang Date: Wed, 19 May 2021 10:05:36 +0800 Subject: [PATCH 540/912] Fix conflict between param and exact path (#2706) * Fix conflict between param and exact path Signed-off-by: Yue Yang * Add test Signed-off-by: Yue Yang * Fix prefix conflict in exact paths Signed-off-by: Yue Yang * Use backtracking Signed-off-by: Yue Yang * Fix panic Signed-off-by: Yue Yang --- tree.go | 29 +++++++++++++++++++++++++++++ tree_test.go | 18 +++++++++++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/tree.go b/tree.go index ca753e6..0d082d0 100644 --- a/tree.go +++ b/tree.go @@ -118,6 +118,11 @@ type node struct { fullPath string } +type skip struct { + path string + paramNode *node +} + // Increments priority of the given child and reorders if necessary func (n *node) incrementChildPrio(pos int) int { cs := n.children @@ -400,6 +405,8 @@ type nodeValue struct { // made if a handle exists with an extra (without the) trailing slash for the // given path. func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) { + var skipped *skip + walk: // Outer loop for walking the tree for { prefix := n.path @@ -411,6 +418,21 @@ walk: // Outer loop for walking the tree idxc := path[0] for i, c := range []byte(n.indices) { if c == idxc { + if strings.HasPrefix(n.children[len(n.children)-1].path, ":") { + skipped = &skip{ + path: prefix + path, + paramNode: &node{ + path: n.path, + wildChild: n.wildChild, + nType: n.nType, + priority: n.priority, + children: n.children, + handlers: n.handlers, + fullPath: n.fullPath, + }, + } + } + n = n.children[i] continue walk } @@ -542,6 +564,13 @@ walk: // Outer loop for walking the tree return } + if path != "/" && skipped != nil && strings.HasSuffix(skipped.path, path) { + path = skipped.path + n = skipped.paramNode + skipped = nil + continue walk + } + // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path value.tsr = (path == "/") || diff --git a/tree_test.go b/tree_test.go index d7c4fb0..298c5ed 100644 --- a/tree_test.go +++ b/tree_test.go @@ -135,13 +135,16 @@ func TestTreeWildcard(t *testing.T) { routes := [...]string{ "/", - "/cmd/:tool/:sub", "/cmd/:tool/", + "/cmd/:tool/:sub", "/cmd/whoami", + "/cmd/whoami/root", "/cmd/whoami/root/", "/src/*filepath", "/search/", "/search/:query", + "/search/gin-gonic", + "/search/google", "/user_:name", "/user_:name/about", "/files/:dir/*filepath", @@ -150,6 +153,7 @@ func TestTreeWildcard(t *testing.T) { "/doc/go1.html", "/info/:user/public", "/info/:user/project/:project", + "/info/:user/project/golang", } for _, route := range routes { tree.addRoute(route, fakeHandler(route)) @@ -159,21 +163,29 @@ func TestTreeWildcard(t *testing.T) { {"/", false, "/", nil}, {"/cmd/test", true, "/cmd/:tool/", Params{Param{"tool", "test"}}}, {"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}}, + {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}}, + {"/cmd/who", true, "/cmd/:tool/", Params{Param{"tool", "who"}}}, + {"/cmd/who/", false, "/cmd/:tool/", Params{Param{"tool", "who"}}}, {"/cmd/whoami", false, "/cmd/whoami", nil}, {"/cmd/whoami/", true, "/cmd/whoami", nil}, + {"/cmd/whoami/r", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}}, + {"/cmd/whoami/r/", true, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}}, + {"/cmd/whoami/root", false, "/cmd/whoami/root", nil}, {"/cmd/whoami/root/", false, "/cmd/whoami/root/", nil}, - {"/cmd/whoami/root", true, "/cmd/whoami/root/", nil}, - {"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}}, {"/src/", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/"}}}, {"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}}, {"/search/", false, "/search/", nil}, {"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}}, {"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}}, + {"/search/gin", false, "/search/:query", Params{Param{"query", "gin"}}}, + {"/search/gin-gonic", false, "/search/gin-gonic", nil}, + {"/search/google", false, "/search/google", nil}, {"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}}, {"/user_gopher/about", false, "/user_:name/about", Params{Param{Key: "name", Value: "gopher"}}}, {"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{Key: "dir", Value: "js"}, Param{Key: "filepath", Value: "/inc/framework.js"}}}, {"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}}, {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}}, + {"/info/gordon/project/golang", false, "/info/:user/project/golang", Params{Param{Key: "user", Value: "gordon"}}}, }) checkPriorities(t, tree) From d7091e7decc336b13a68c648433312778704692d Mon Sep 17 00:00:00 2001 From: yugu Date: Wed, 19 May 2021 10:57:23 +0800 Subject: [PATCH 541/912] README.md update (#2715) Co-authored-by: Bo-Yi Wu --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f9178c..38c6748 100644 --- a/README.md +++ b/README.md @@ -702,7 +702,7 @@ func main() { // Example for binding XML ( // // - // user + // manu // 123 // ) router.POST("/loginXML", func(c *gin.Context) { From e72e584d1abae00fb42ef9aba1ea262c062ba2dc Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 20 May 2021 07:57:55 +0800 Subject: [PATCH 542/912] chore(docs): bump to v1.7.2 (#2724) * chore(docs): bump to v1.7.2 Signed-off-by: Bo-Yi Wu * chore: add change log Signed-off-by: Bo-Yi Wu --- CHANGELOG.md | 6 ++++++ version.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2c2f5..a28edc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Gin ChangeLog +## Gin v1.7.2 + +### BUGFIXES + +* Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696). + ## Gin v1.7.1 ### BUGFIXES diff --git a/version.go b/version.go index 3647461..a80ab69 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.7.1" +const Version = "v1.7.2" From afb38396b54a3c0d54e1adf5f3b9896114c1a425 Mon Sep 17 00:00:00 2001 From: tyltr <31768692+tylitianrui@users.noreply.github.com> Date: Sat, 22 May 2021 13:17:19 +0800 Subject: [PATCH 543/912] optimize code (#2722) Co-authored-by: Bo-Yi Wu --- context.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index dc03c35..12e946d 100644 --- a/context.go +++ b/context.go @@ -86,17 +86,17 @@ type Context struct { func (c *Context) reset() { c.Writer = &c.writermem - c.Params = c.Params[0:0] + c.Params = c.Params[:0] c.handlers = nil c.index = -1 c.fullPath = "" c.Keys = nil - c.Errors = c.Errors[0:0] + c.Errors = c.Errors[:0] c.Accepted = nil c.queryCache = nil c.formCache = nil - *c.params = (*c.params)[0:0] + *c.params = (*c.params)[:0] } // Copy returns a copy of the current context that can be safely used outside the request's scope. From f13e53bb92b7154c9722bd600387a6814deb6b1d Mon Sep 17 00:00:00 2001 From: likakuli <1154584512@qq.com> Date: Sun, 23 May 2021 09:54:54 +0800 Subject: [PATCH 544/912] upgrade validator to v10.6.1 (#2729) Co-authored-by: Bo-Yi Wu --- go.mod | 3 ++- go.sum | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 884ff85..d18da17 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.4.1 + github.com/go-playground/validator/v10 v10.6.1 + github.com/goccy/go-json v0.5.1 github.com/golang/protobuf v1.3.3 github.com/json-iterator/go v1.1.9 github.com/mattn/go-isatty v0.0.12 diff --git a/go.sum b/go.sum index ac92ada..e30155c 100644 --- a/go.sum +++ b/go.sum @@ -9,10 +9,10 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/goccy/go-json v0.4.11 h1:92nyX606ZN/cUFwctfxwDWm8YWSA38Zlv9s7taFeLyo= -github.com/goccy/go-json v0.4.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I= +github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= +github.com/goccy/go-json v0.5.1 h1:R9UYTOUvo7eIY9aeDMZ4L6OVtHaSr1k2No9W6MKjXrA= +github.com/goccy/go-json v0.5.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -45,6 +45,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 168edcad805472702808f80204cb5d4b607ff0d6 Mon Sep 17 00:00:00 2001 From: y-yagi Date: Sun, 23 May 2021 12:44:41 +0900 Subject: [PATCH 545/912] Check multipart file header size on test (#2716) Co-authored-by: Bo-Yi Wu Co-authored-by: thinkerou --- binding/multipart_form_mapping_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go index 4c75d1f..4aaa60b 100644 --- a/binding/multipart_form_mapping_test.go +++ b/binding/multipart_form_mapping_test.go @@ -124,7 +124,7 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) { assert.Equal(t, file.Filename, fh.Filename) - // assert.Equal(t, int64(len(file.Content)), fh.Size) // fh.Size does not exist on go1.8 + assert.Equal(t, int64(len(file.Content)), fh.Size) fl, err := fh.Open() assert.NoError(t, err) From f07a4f8aea8ba84dcfe46196f3c43aa3f4e2349e Mon Sep 17 00:00:00 2001 From: sunshineplan Date: Mon, 24 May 2021 08:31:22 +0800 Subject: [PATCH 546/912] Upgrade github.com/ugorji/go/codec (#2732) --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index d18da17..9484b26 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,6 @@ require ( github.com/json-iterator/go v1.1.9 github.com/mattn/go-isatty v0.0.12 github.com/stretchr/testify v1.4.0 - github.com/ugorji/go/codec v1.1.7 + github.com/ugorji/go/codec v1.2.6 gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index e30155c..e61ef90 100644 --- a/go.sum +++ b/go.sum @@ -32,10 +32,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= +github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= +github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= +github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= From 328d0b8076f08b58efbe052f9fa8b7ea0807bddd Mon Sep 17 00:00:00 2001 From: Don2Quixote <35610661+Don2Quixote@users.noreply.github.com> Date: Mon, 24 May 2021 11:55:54 +0300 Subject: [PATCH 547/912] Fixed typo in documentation (#2733) --- fs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs.go b/fs.go index 007d9b7..e5f3d60 100644 --- a/fs.go +++ b/fs.go @@ -17,7 +17,7 @@ type neuteredReaddirFile struct { http.File } -// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally +// Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally // in router.Static(). // if listDirectory == true, then it works the same as http.Dir() otherwise it returns // a filesystem that prevents http.FileServer() to list the directory files. From b5ca9898757b45bbdcc1464b73a12f3ca552f94d Mon Sep 17 00:00:00 2001 From: yiranzai Date: Tue, 25 May 2021 13:47:35 +0800 Subject: [PATCH 548/912] set engine.TrustedProxies For items that don't use gin.RUN (#2692) Co-authored-by: Bo-Yi Wu --- context_test.go | 36 +++++++---------------- gin.go | 38 +++++++++++++++++++++++-- gin_integration_test.go | 63 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 28 deletions(-) diff --git a/context_test.go b/context_test.go index 993c632..aaa358e 100644 --- a/context_test.go +++ b/context_test.go @@ -1392,14 +1392,10 @@ func TestContextAbortWithError(t *testing.T) { assert.True(t, c.IsAborted()) } -func resetTrustedCIDRs(c *Context) { - c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs() -} - func TestContextClientIP(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) - resetTrustedCIDRs(c) + c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs() resetContextForClientIPTests(c) // Legacy tests (validating that the defaults don't break the @@ -1428,57 +1424,47 @@ func TestContextClientIP(t *testing.T) { resetContextForClientIPTests(c) // No trusted proxies - c.engine.TrustedProxies = []string{} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{}) c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"} assert.Equal(t, "40.40.40.40", c.ClientIP()) // Last proxy is trusted, but the RemoteAddr is not - c.engine.TrustedProxies = []string{"30.30.30.30"} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{"30.30.30.30"}) assert.Equal(t, "40.40.40.40", c.ClientIP()) // Only trust RemoteAddr - c.engine.TrustedProxies = []string{"40.40.40.40"} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{"40.40.40.40"}) assert.Equal(t, "20.20.20.20", c.ClientIP()) // All steps are trusted - c.engine.TrustedProxies = []string{"40.40.40.40", "30.30.30.30", "20.20.20.20"} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{"40.40.40.40", "30.30.30.30", "20.20.20.20"}) assert.Equal(t, "20.20.20.20", c.ClientIP()) // Use CIDR - c.engine.TrustedProxies = []string{"40.40.25.25/16", "30.30.30.30"} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{"40.40.25.25/16", "30.30.30.30"}) assert.Equal(t, "20.20.20.20", c.ClientIP()) // Use hostname that resolves to all the proxies - c.engine.TrustedProxies = []string{"foo"} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{"foo"}) assert.Equal(t, "40.40.40.40", c.ClientIP()) // Use hostname that returns an error - c.engine.TrustedProxies = []string{"bar"} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{"bar"}) assert.Equal(t, "40.40.40.40", c.ClientIP()) // X-Forwarded-For has a non-IP element - c.engine.TrustedProxies = []string{"40.40.40.40"} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{"40.40.40.40"}) c.Request.Header.Set("X-Forwarded-For", " blah ") assert.Equal(t, "40.40.40.40", c.ClientIP()) // Result from LookupHost has non-IP element. This should never // happen, but we should test it to make sure we handle it // gracefully. - c.engine.TrustedProxies = []string{"baz"} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{"baz"}) c.Request.Header.Set("X-Forwarded-For", " 30.30.30.30 ") assert.Equal(t, "40.40.40.40", c.ClientIP()) - c.engine.TrustedProxies = []string{"40.40.40.40"} - resetTrustedCIDRs(c) + _ = c.engine.SetTrustedProxies([]string{"40.40.40.40"}) c.Request.Header.Del("X-Forwarded-For") c.engine.RemoteIPHeaders = []string{"X-Forwarded-For", "X-Real-IP"} assert.Equal(t, "10.10.10.10", c.ClientIP()) diff --git a/gin.go b/gin.go index 03a0e12..56e5c76 100644 --- a/gin.go +++ b/gin.go @@ -326,11 +326,11 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() - trustedCIDRs, err := engine.prepareTrustedCIDRs() + err = engine.parseTrustedProxies() if err != nil { return err } - engine.trustedCIDRs = trustedCIDRs + address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine) @@ -366,6 +366,19 @@ func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) { return cidr, nil } +// SetTrustedProxies set Engine.TrustedProxies +func (engine *Engine) SetTrustedProxies(trustedProxies []string) error { + engine.TrustedProxies = trustedProxies + return engine.parseTrustedProxies() +} + +// parseTrustedProxies parse Engine.TrustedProxies to Engine.trustedCIDRs +func (engine *Engine) parseTrustedProxies() error { + trustedCIDRs, err := engine.prepareTrustedCIDRs() + engine.trustedCIDRs = trustedCIDRs + return err +} + // parseIP parse a string representation of an IP and returns a net.IP with the // minimum byte representation or nil if input is invalid. func parseIP(ip string) net.IP { @@ -387,6 +400,11 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { debugPrint("Listening and serving HTTPS on %s\n", addr) defer func() { debugPrintError(err) }() + err = engine.parseTrustedProxies() + if err != nil { + return err + } + err = http.ListenAndServeTLS(addr, certFile, keyFile, engine) return } @@ -398,6 +416,11 @@ func (engine *Engine) RunUnix(file string) (err error) { debugPrint("Listening and serving HTTP on unix:/%s", file) defer func() { debugPrintError(err) }() + err = engine.parseTrustedProxies() + if err != nil { + return err + } + listener, err := net.Listen("unix", file) if err != nil { return @@ -416,6 +439,11 @@ func (engine *Engine) RunFd(fd int) (err error) { debugPrint("Listening and serving HTTP on fd@%d", fd) defer func() { debugPrintError(err) }() + err = engine.parseTrustedProxies() + if err != nil { + return err + } + f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) listener, err := net.FileListener(f) if err != nil { @@ -431,6 +459,12 @@ func (engine *Engine) RunFd(fd int) (err error) { func (engine *Engine) RunListener(listener net.Listener) (err error) { debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr()) defer func() { debugPrintError(err) }() + + err = engine.parseTrustedProxies() + if err != nil { + return err + } + err = http.Serve(listener, engine) return } diff --git a/gin_integration_test.go b/gin_integration_test.go index fd97265..2eb2d52 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -55,13 +55,74 @@ func TestRunEmpty(t *testing.T) { testRequest(t, "http://localhost:8080/example") } -func TestTrustedCIDRsForRun(t *testing.T) { +func TestBadTrustedCIDRsForRun(t *testing.T) { os.Setenv("PORT", "") router := New() router.TrustedProxies = []string{"hello/world"} assert.Error(t, router.Run(":8080")) } +func TestBadTrustedCIDRsForRunUnix(t *testing.T) { + router := New() + router.TrustedProxies = []string{"hello/world"} + + unixTestSocket := filepath.Join(os.TempDir(), "unix_unit_test") + + defer os.Remove(unixTestSocket) + + go func() { + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + assert.Error(t, router.RunUnix(unixTestSocket)) + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) +} + +func TestBadTrustedCIDRsForRunFd(t *testing.T) { + router := New() + router.TrustedProxies = []string{"hello/world"} + + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + assert.NoError(t, err) + listener, err := net.ListenTCP("tcp", addr) + assert.NoError(t, err) + socketFile, err := listener.File() + assert.NoError(t, err) + + go func() { + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + assert.Error(t, router.RunFd(int(socketFile.Fd()))) + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) +} + +func TestBadTrustedCIDRsForRunListener(t *testing.T) { + router := New() + router.TrustedProxies = []string{"hello/world"} + + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + assert.NoError(t, err) + listener, err := net.ListenTCP("tcp", addr) + assert.NoError(t, err) + go func() { + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + assert.Error(t, router.RunListener(listener)) + }() + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) +} + +func TestBadTrustedCIDRsForRunTLS(t *testing.T) { + os.Setenv("PORT", "") + router := New() + router.TrustedProxies = []string{"hello/world"} + assert.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) +} + func TestRunTLS(t *testing.T) { router := New() go func() { From 0cbb30aa940a643e31874f8ad8e355219d306756 Mon Sep 17 00:00:00 2001 From: iamhesir <78344375+iamhesir@users.noreply.github.com> Date: Wed, 26 May 2021 18:46:13 +0800 Subject: [PATCH 549/912] Update default validator's docs link (#2738) The default validator has upgraded from v8 to v10, but its docs link didn't. --- binding/default_validator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index c57a120..ee69329 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -71,7 +71,7 @@ func (v *defaultValidator) validateStruct(obj interface{}) error { // Engine returns the underlying validator engine which powers the default // Validator instance. This is useful if you want to register custom validations // or struct level validations. See validator GoDoc for more info - -// https://godoc.org/gopkg.in/go-playground/validator.v8 +// https://pkg.go.dev/github.com/go-playground/validator/v10 func (v *defaultValidator) Engine() interface{} { v.lazyinit() return v.validate From 6703dea51c3a32d6b30f51ba0d6a7a7deae23e32 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Thu, 27 May 2021 19:03:59 -0700 Subject: [PATCH 550/912] Get client IP when using Cloudflare (#2723) Co-authored-by: thinkerou --- context.go | 7 ++++++- context_test.go | 8 ++++++++ gin.go | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 12e946d..0c1fb07 100644 --- a/context.go +++ b/context.go @@ -731,10 +731,15 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e // If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy, // the remote IP (coming form Request.RemoteAddr) is returned. func (c *Context) ClientIP() string { - if c.engine.AppEngine { + switch { + case c.engine.AppEngine: if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { return addr } + case c.engine.CloudflareProxy: + if addr := c.requestHeader("CF-Connecting-IP"); addr != "" { + return addr + } } remoteIP, trusted := c.RemoteIP() diff --git a/context_test.go b/context_test.go index aaa358e..e0de717 100644 --- a/context_test.go +++ b/context_test.go @@ -1476,6 +1476,13 @@ func TestContextClientIP(t *testing.T) { c.Request.Header.Del("X-Appengine-Remote-Addr") assert.Equal(t, "40.40.40.40", c.ClientIP()) + c.engine.AppEngine = false + c.engine.CloudflareProxy = true + assert.Equal(t, "60.60.60.60", c.ClientIP()) + + c.Request.Header.Del("CF-Connecting-IP") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + // no port c.Request.RemoteAddr = "50.50.50.50" assert.Empty(t, c.ClientIP()) @@ -1485,6 +1492,7 @@ func resetContextForClientIPTests(c *Context) { c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ") c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30") c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50") + c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60") c.Request.RemoteAddr = " 40.40.40.40:42123 " c.engine.AppEngine = false } diff --git a/gin.go b/gin.go index 56e5c76..00686e7 100644 --- a/gin.go +++ b/gin.go @@ -105,6 +105,10 @@ type Engine struct { // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool + // If enabled, it will trust the CF-Connecting-IP header to determine the + // IP of the client. + CloudflareProxy bool + // If enabled, the url.RawPath will be used to find parameters. UseRawPath bool From 97a32b1de36fe44e0f58e7e1772f63e63879902e Mon Sep 17 00:00:00 2001 From: heige Date: Wed, 2 Jun 2021 07:35:30 +0800 Subject: [PATCH 551/912] Optimize code adjust (#2700) * setFormMap error of result * adjust code for TrySet * error export for type multipart.FileHeader * code style adjust * reflect code maping optimize * Update form_mapping.go Co-authored-by: Bo-Yi Wu Co-authored-by: thinkerou --- binding/form_mapping.go | 16 ++++++++++++---- binding/header.go | 2 +- binding/multipart_form_mapping.go | 14 +++++++++++--- render/json.go | 8 +++++--- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 2f4e45b..421c0f7 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -16,7 +16,15 @@ import ( "github.com/gin-gonic/gin/internal/json" ) -var errUnknownType = errors.New("unknown type") +var ( + errUnknownType = errors.New("unknown type") + + // ErrConvertMapStringSlice can not covert to map[string][]string + ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings") + + // ErrConvertToMapString can not convert to map[string]string + ErrConvertToMapString = errors.New("can not convert to map of strings") +) func mapUri(ptr interface{}, m map[string][]string) error { return mapFormByTag(ptr, m, "uri") @@ -109,7 +117,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag if sf.PkgPath != "" && !sf.Anonymous { // unexported continue } - ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) + ok, err := mapping(value.Field(i), sf, setter, tag) if err != nil { return false, err } @@ -371,7 +379,7 @@ func setFormMap(ptr interface{}, form map[string][]string) error { if el.Kind() == reflect.Slice { ptrMap, ok := ptr.(map[string][]string) if !ok { - return errors.New("cannot convert to map slices of strings") + return ErrConvertMapStringSlice } for k, v := range form { ptrMap[k] = v @@ -382,7 +390,7 @@ func setFormMap(ptr interface{}, form map[string][]string) error { ptrMap, ok := ptr.(map[string]string) if !ok { - return errors.New("cannot convert to map of strings") + return ErrConvertToMapString } for k, v := range form { ptrMap[k] = v[len(v)-1] // pick last diff --git a/binding/header.go b/binding/header.go index 179ce4e..b99302a 100644 --- a/binding/header.go +++ b/binding/header.go @@ -29,6 +29,6 @@ type headerSource map[string][]string var _ setter = headerSource(nil) -func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { +func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (bool, error) { return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt) } diff --git a/binding/multipart_form_mapping.go b/binding/multipart_form_mapping.go index f85a1aa..69c0a54 100644 --- a/binding/multipart_form_mapping.go +++ b/binding/multipart_form_mapping.go @@ -15,8 +15,16 @@ type multipartRequest http.Request var _ setter = (*multipartRequest)(nil) +var ( + // ErrMultiFileHeader multipart.FileHeader invalid + ErrMultiFileHeader = errors.New("unsupported field type for multipart.FileHeader") + + // ErrMultiFileHeaderLenInvalid array for []*multipart.FileHeader len invalid + ErrMultiFileHeaderLenInvalid = errors.New("unsupported len of array for []*multipart.FileHeader") +) + // TrySet tries to set a value by the multipart request with the binding a form file -func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { +func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (bool, error) { if files := r.MultipartForm.File[key]; len(files) != 0 { return setByMultipartFormFile(value, field, files) } @@ -49,12 +57,12 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file case reflect.Array: return setArrayOfMultipartFormFiles(value, field, files) } - return false, errors.New("unsupported field type for multipart.FileHeader") + return false, ErrMultiFileHeader } func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { if value.Len() != len(files) { - return false, errors.New("unsupported len of array for []*multipart.FileHeader") + return false, ErrMultiFileHeaderLenInvalid } for i := range files { setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1]) diff --git a/render/json.go b/render/json.go index 4186309..e25415b 100644 --- a/render/json.go +++ b/render/json.go @@ -46,9 +46,11 @@ type PureJSON struct { Data interface{} } -var jsonContentType = []string{"application/json; charset=utf-8"} -var jsonpContentType = []string{"application/javascript; charset=utf-8"} -var jsonAsciiContentType = []string{"application/json"} +var ( + jsonContentType = []string{"application/json; charset=utf-8"} + jsonpContentType = []string{"application/javascript; charset=utf-8"} + jsonAsciiContentType = []string{"application/json"} +) // Render (JSON) writes data with custom ContentType. func (r JSON) Render(w http.ResponseWriter) (err error) { From 34ce2104cad324f444943c528746bf6d23643cd3 Mon Sep 17 00:00:00 2001 From: tyltr <31768692+tylitianrui@users.noreply.github.com> Date: Thu, 3 Jun 2021 20:12:51 +0800 Subject: [PATCH 552/912] optimize code and reduce code cyclomatic complexity (#2737) * optimize code and reduce code cyclomatic complexity * optimize if-condtion Co-authored-by: thinkerou --- binding/form.go | 6 ++---- debug.go | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/binding/form.go b/binding/form.go index b93c34c..040af9e 100644 --- a/binding/form.go +++ b/binding/form.go @@ -22,10 +22,8 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } - if err := req.ParseMultipartForm(defaultMemory); err != nil { - if err != http.ErrNotMultipart { - return err - } + if err := req.ParseMultipartForm(defaultMemory); err != nil && err != http.ErrNotMultipart { + return err } if err := mapForm(obj, req.Form); err != nil { return err diff --git a/debug.go b/debug.go index 9bacc68..ed31386 100644 --- a/debug.go +++ b/debug.go @@ -95,9 +95,7 @@ at initialization. ie. before any route is registered or the router is listening } func debugPrintError(err error) { - if err != nil { - if IsDebugging() { - fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err) - } + if err != nil && IsDebugging() { + fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err) } } From 61a0cda75a997e61d99ebf9dfbbac28aadf1350b Mon Sep 17 00:00:00 2001 From: youzeliang Date: Wed, 23 Jun 2021 06:44:39 +0800 Subject: [PATCH 553/912] Update tree.go (#2659) delete more "()" --- tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index 0d082d0..051c74a 100644 --- a/tree.go +++ b/tree.go @@ -487,7 +487,7 @@ walk: // Outer loop for walking the tree } // ... but we can't - value.tsr = (len(path) == end+1) + value.tsr = len(path) == end+1 return } @@ -499,7 +499,7 @@ walk: // Outer loop for walking the tree // No handle found. Check if a handle for this path + a // trailing slash exists for TSR recommendation n = n.children[0] - value.tsr = (n.path == "/" && n.handlers != nil) + value.tsr = n.path == "/" && n.handlers != nil } return From a8857ed70a32803c0fe4508364635713d9cd3968 Mon Sep 17 00:00:00 2001 From: Ashwani Date: Wed, 23 Jun 2021 09:06:24 +0530 Subject: [PATCH 554/912] updated comments for Get function for params (#2756) --- tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index 051c74a..5f35fff 100644 --- a/tree.go +++ b/tree.go @@ -30,8 +30,8 @@ type Param struct { // It is therefore safe to read values by the index. type Params []Param -// Get returns the value of the first Param which key matches the given name. -// If no matching Param is found, an empty string is returned. +// Get returns the value of the first Param which key matches the given name and a boolean true. +// If no matching Param is found, an empty string is returned and a boolean false . func (ps Params) Get(name string) (string, bool) { for _, entry := range ps { if entry.Key == name { From fb8a113f8d2d526e57d3623dbd4b9fa12c40d0f7 Mon Sep 17 00:00:00 2001 From: Jeff Date: Wed, 23 Jun 2021 13:10:49 +0800 Subject: [PATCH 555/912] ci: add github action workflows (#2596) * ci: add github action workflows * test: fixed the TestUnixSocket test on windows (#20) * ci: add github action workflows (#18) * Remove .travis.yml * ci: replace GITTER_ROOM_ID and upload coverage every time you go test * ci: update coverage using codecov/codecov-action@v1 * Merge branch 'master' into github-actions * repo: replace travis ci to github actions * ci: add go version 1.16 * fix: go install requires a specific version * chore(ci): remove go 1.12 support * chore(ci): remove os windows-latest Co-authored-by: thinkerou Co-authored-by: Bo-Yi Wu --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/gin.yml | 62 ++++++++++++++++++++++++++++++++ .travis.yml | 52 --------------------------- CONTRIBUTING.md | 2 +- Makefile | 10 ++++-- README.md | 2 +- 6 files changed, 73 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/gin.yml delete mode 100644 .travis.yml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e86bc98..96e70bb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,7 @@ - With pull requests: - Open your pull request against `master` - Your pull request should have no more than two commits, if not you should squash them. - - It should pass all tests in the available continuous integration systems such as TravisCI. + - It should pass all tests in the available continuous integration systems such as GitHub Actions. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README. diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml new file mode 100644 index 0000000..85cc560 --- /dev/null +++ b/.github/workflows/gin.yml @@ -0,0 +1,62 @@ +name: Run Tests + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + go: [1.13, 1.14, 1.15, 1.16] + test-tags: ['', nomsgpack] + name: ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} + runs-on: ${{ matrix.os }} + env: + GO111MODULE: on + TESTTAGS: ${{ matrix.test-tags }} + GOPROXY: https://proxy.golang.org + steps: + - name: Set up Go ${{ matrix.go }} + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + + - name: Checkout Code + uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} + + - name: Install Dependencies + run: make tools + + - name: Run Check + run: | + make vet + make fmt-check + make misspell-check + + - name: Run Tests + run: make test + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + notification-gitter: + needs: test + runs-on: ubuntu-latest + steps: + - name: Notification failure message + if: failure() + run: | + PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)" + curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" -d level=error https://webhooks.gitter.im/e/7f95bf605c4d356372f4 + - name: Notification success message + if: success() + run: | + PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)" + curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" https://webhooks.gitter.im/e/7f95bf605c4d356372f4 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8166231..0000000 --- a/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -language: go - -matrix: - fast_finish: true - include: - - go: 1.13.x - - go: 1.13.x - env: - - TESTTAGS=nomsgpack - - go: 1.14.x - - go: 1.14.x - env: - - TESTTAGS=nomsgpack - - go: 1.15.x - - go: 1.15.x - env: - - TESTTAGS=nomsgpack - - go: 1.16.x - - go: 1.16.x - env: - - TESTTAGS=nomsgpack - - go: master - -git: - depth: 10 - -before_install: - - if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi - -install: - - if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi - - if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi - - if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi - -go_import_path: github.com/gin-gonic/gin - -script: - - make vet - - make fmt-check - - make misspell-check - - make test - -after_success: - - bash <(curl -s https://codecov.io/bash) - -notifications: - webhooks: - urls: - - https://webhooks.gitter.im/e/7f95bf605c4d356372f4 - on_success: change # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always - on_start: false # default: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97daa80..d1c723c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,6 @@ - With pull requests: - Open your pull request against `master` - Your pull request should have no more than two commits, if not you should squash them. - - It should pass all tests in the available continuous integration systems such as TravisCI. + - It should pass all tests in the available continuous integration systems such as GitHub Actions. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README. diff --git a/Makefile b/Makefile index 1a99193..5d55b44 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ GO ?= go GOFMT ?= gofmt "-s" +GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2) PACKAGES ?= $(shell $(GO) list ./...) VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/) GOFILES := $(shell find . -name "*.go") @@ -67,5 +68,10 @@ misspell: .PHONY: tools tools: - go install golang.org/x/lint/golint; \ - go install github.com/client9/misspell/cmd/misspell; + @if [ $(GO_VERSION) -gt 15 ]; then \ + $(GO) install golang.org/x/lint/golint@latest; \ + $(GO) install github.com/client9/misspell/cmd/misspell@latest; \ + elif [ $(GO_VERSION) -lt 16 ]; then \ + $(GO) install golang.org/x/lint/golint; \ + $(GO) install github.com/client9/misspell/cmd/misspell; \ + fi diff --git a/README.md b/README.md index 38c6748..ef37fd2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin) +[![Build Status](https://github.com/gin-gonic/gin/workflows/Run%20Tests/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) From dd8a27c0b6fbbfd192c5586ba3165700f535ac7f Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 23 Jun 2021 17:58:10 -0700 Subject: [PATCH 556/912] Setting trusted platform using an enum-like (#2739) --- context.go | 16 +++++++++++++--- context_appengine.go | 2 +- context_test.go | 15 ++++++++++++--- gin.go | 23 +++++++++++++++++------ 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/context.go b/context.go index 0c1fb07..5c1aba5 100644 --- a/context.go +++ b/context.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "io/ioutil" + "log" "math" "mime/multipart" "net" @@ -731,17 +732,26 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e // If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy, // the remote IP (coming form Request.RemoteAddr) is returned. func (c *Context) ClientIP() string { - switch { - case c.engine.AppEngine: + // Check if we're running on a tursted platform + switch c.engine.TrustedPlatform { + case PlatformGoogleAppEngine: if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { return addr } - case c.engine.CloudflareProxy: + case PlatformCloudflare: if addr := c.requestHeader("CF-Connecting-IP"); addr != "" { return addr } } + // Legacy "AppEngine" flag + if c.engine.AppEngine { + log.Println(`The AppEngine flag is going to be deprecated. Please check issues #2723 and #2739 and use 'TrustedPlatform: gin.PlatformGoogleAppEngine' instead.`) + if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { + return addr + } + } + remoteIP, trusted := c.RemoteIP() if remoteIP == nil { return "" diff --git a/context_appengine.go b/context_appengine.go index d565843..8bf9389 100644 --- a/context_appengine.go +++ b/context_appengine.go @@ -8,5 +8,5 @@ package gin func init() { - defaultAppEngine = true + defaultPlatform = PlatformGoogleAppEngine } diff --git a/context_test.go b/context_test.go index e0de717..93b58ee 100644 --- a/context_test.go +++ b/context_test.go @@ -1410,7 +1410,7 @@ func TestContextClientIP(t *testing.T) { c.Request.Header.Del("X-Forwarded-For") c.Request.Header.Del("X-Real-IP") - c.engine.AppEngine = true + c.engine.TrustedPlatform = PlatformGoogleAppEngine assert.Equal(t, "50.50.50.50", c.ClientIP()) c.Request.Header.Del("X-Appengine-Remote-Addr") @@ -1470,19 +1470,27 @@ func TestContextClientIP(t *testing.T) { assert.Equal(t, "10.10.10.10", c.ClientIP()) c.engine.RemoteIPHeaders = []string{} + c.engine.TrustedPlatform = PlatformGoogleAppEngine + assert.Equal(t, "50.50.50.50", c.ClientIP()) + + // Test the legacy flag + c.engine.TrustedPlatform = "" c.engine.AppEngine = true assert.Equal(t, "50.50.50.50", c.ClientIP()) + c.engine.AppEngine = false + c.engine.TrustedPlatform = PlatformGoogleAppEngine c.Request.Header.Del("X-Appengine-Remote-Addr") assert.Equal(t, "40.40.40.40", c.ClientIP()) - c.engine.AppEngine = false - c.engine.CloudflareProxy = true + c.engine.TrustedPlatform = PlatformCloudflare assert.Equal(t, "60.60.60.60", c.ClientIP()) c.Request.Header.Del("CF-Connecting-IP") assert.Equal(t, "40.40.40.40", c.ClientIP()) + c.engine.TrustedPlatform = "" + // no port c.Request.RemoteAddr = "50.50.50.50" assert.Empty(t, c.ClientIP()) @@ -1494,6 +1502,7 @@ func resetContextForClientIPTests(c *Context) { c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50") c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60") c.Request.RemoteAddr = " 40.40.40.40:42123 " + c.engine.TrustedPlatform = "" c.engine.AppEngine = false } diff --git a/gin.go b/gin.go index 00686e7..fd4fa51 100644 --- a/gin.go +++ b/gin.go @@ -25,7 +25,7 @@ var ( default405Body = []byte("405 method not allowed") ) -var defaultAppEngine bool +var defaultPlatform string // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -52,6 +52,16 @@ type RouteInfo struct { // RoutesInfo defines a RouteInfo array. type RoutesInfo []RouteInfo +// Trusted platforms +const ( + // When running on Google App Engine. Trust X-Appengine-Remote-Addr + // for determining the client's IP + PlatformGoogleAppEngine = "google-app-engine" + // When using Cloudflare's CDN. Trust CF-Connecting-IP for determining + // the client's IP + PlatformCloudflare = "cloudflare" +) + // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. // Create an instance of Engine, by using New() or Default() type Engine struct { @@ -101,14 +111,15 @@ type Engine struct { // `true`. TrustedProxies []string + // If set to a constant of value gin.Platform*, trusts the headers set by + // that platform, for example to determine the client IP + TrustedPlatform string + + // DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.GoogleAppEngine` INSTEAD // #726 #755 If enabled, it will trust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool - // If enabled, it will trust the CF-Connecting-IP header to determine the - // IP of the client. - CloudflareProxy bool - // If enabled, the url.RawPath will be used to find parameters. UseRawPath bool @@ -164,7 +175,7 @@ func New() *Engine { ForwardedByClientIP: true, RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"}, TrustedProxies: []string{"0.0.0.0/0"}, - AppEngine: defaultAppEngine, + TrustedPlatform: defaultPlatform, UseRawPath: false, RemoveExtraSlash: false, UnescapePathValues: true, From be860ec1578f0c9d1e220934455e9507d3a4c1e6 Mon Sep 17 00:00:00 2001 From: ziheng Date: Thu, 24 Jun 2021 13:07:49 +0800 Subject: [PATCH 557/912] fix typo and add comments (#2760) --- recovery.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 563f5aa..8519959 100644 --- a/recovery.go +++ b/recovery.go @@ -34,7 +34,7 @@ func Recovery() HandlerFunc { return RecoveryWithWriter(DefaultErrorWriter) } -//CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it. +// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it. func CustomRecovery(handle RecoveryFunc) HandlerFunc { return RecoveryWithWriter(DefaultErrorWriter, handle) } @@ -165,6 +165,7 @@ func function(pc uintptr) []byte { return name } +// timeFormat returns a customized time string for logger. func timeFormat(t time.Time) string { timeString := t.Format("2006/01/02 - 15:04:05") return timeString From 09f6cff92a319b080789c8d20aa4cc6efbe876fe Mon Sep 17 00:00:00 2001 From: ziheng Date: Thu, 24 Jun 2021 15:31:38 +0800 Subject: [PATCH 558/912] skip unnecessary variable assignment in timeFormat (#2761) --- recovery.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/recovery.go b/recovery.go index 8519959..3101fe2 100644 --- a/recovery.go +++ b/recovery.go @@ -167,6 +167,5 @@ func function(pc uintptr) []byte { // timeFormat returns a customized time string for logger. func timeFormat(t time.Time) string { - timeString := t.Format("2006/01/02 - 15:04:05") - return timeString + return t.Format("2006/01/02 - 15:04:05") } From 7834a03e8467c1b58eda0efd58374896f240f69c Mon Sep 17 00:00:00 2001 From: wei Date: Thu, 24 Jun 2021 16:33:14 +0800 Subject: [PATCH 559/912] gin.Context with fallback value from gin.Context.Request.Context() (#2751) * Update tree.go (#2659) delete more "()" * updated comments for Get function for params (#2756) * ci: add github action workflows (#2596) * ci: add github action workflows * test: fixed the TestUnixSocket test on windows (#20) * ci: add github action workflows (#18) * Remove .travis.yml * ci: replace GITTER_ROOM_ID and upload coverage every time you go test * ci: update coverage using codecov/codecov-action@v1 * Merge branch 'master' into github-actions * repo: replace travis ci to github actions * ci: add go version 1.16 * fix: go install requires a specific version * chore(ci): remove go 1.12 support * chore(ci): remove os windows-latest Co-authored-by: thinkerou Co-authored-by: Bo-Yi Wu * Setting trusted platform using an enum-like (#2739) * gin.Context with fallback value from c.Request.Context() * add test case Co-authored-by: youzeliang Co-authored-by: Ashwani Co-authored-by: Jeff Co-authored-by: thinkerou Co-authored-by: Bo-Yi Wu Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com> --- context.go | 10 +++++++--- context_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 5c1aba5..ede16fd 100644 --- a/context.go +++ b/context.go @@ -1186,8 +1186,12 @@ func (c *Context) Value(key interface{}) interface{} { return c.Request } if keyAsString, ok := key.(string); ok { - val, _ := c.Get(keyAsString) - return val + if val, exists := c.Get(keyAsString); exists { + return val + } } - return nil + if c.Request == nil || c.Request.Context() == nil { + return nil + } + return c.Request.Context().Value(key) } diff --git a/context_test.go b/context_test.go index 93b58ee..2a4d218 100644 --- a/context_test.go +++ b/context_test.go @@ -2057,3 +2057,56 @@ func TestRemoteIPFail(t *testing.T) { assert.Nil(t, ip) assert.False(t, trust) } + +func TestContextWithFallbackValueFromRequestContext(t *testing.T) { + tests := []struct { + name string + getContextAndKey func() (*Context, interface{}) + value interface{} + }{ + { + name: "c with struct context key", + getContextAndKey: func() (*Context, interface{}) { + var key struct{} + c := &Context{} + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value")) + return c, key + }, + value: "value", + }, + { + name: "c with string context key", + getContextAndKey: func() (*Context, interface{}) { + c := &Context{} + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request = c.Request.WithContext(context.WithValue(context.TODO(), "key", "value")) + return c, "key" + }, + value: "value", + }, + { + name: "c with nil http.Request", + getContextAndKey: func() (*Context, interface{}) { + c := &Context{} + return c, "key" + }, + value: nil, + }, + { + name: "c with nil http.Request.Context()", + getContextAndKey: func() (*Context, interface{}) { + c := &Context{} + c.Request, _ = http.NewRequest("POST", "/", nil) + return c, "key" + }, + value: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, key := tt.getContextAndKey() + assert.Equal(t, tt.value, c.Value(key)) + }) + } +} From f2bbdfe9f26d84cb994f381050692a9e4553bf75 Mon Sep 17 00:00:00 2001 From: ziheng Date: Fri, 25 Jun 2021 12:14:06 +0800 Subject: [PATCH 560/912] Use buf.String() instead of string(buf.Bytes()) (#2764) --- render/msgpack.go | 2 ++ render/render_msgpack_test.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/render/msgpack.go b/render/msgpack.go index 6ef5b6e..7f17ca4 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -13,6 +13,8 @@ import ( "github.com/ugorji/go/codec" ) +// Check interface implemented here to support go build tag nomsgpack. +// See: https://github.com/gin-gonic/gin/pull/1852/ var ( _ Render = MsgPack{} ) diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index 8170fbe..d16cf6e 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -39,6 +39,6 @@ func TestRenderMsgPack(t *testing.T) { err = codec.NewEncoder(buf, h).Encode(data) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), string(buf.Bytes())) + assert.Equal(t, w.Body.String(), buf.String()) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } From 1d0f938f28d7e592623264591605dc22ac141cc5 Mon Sep 17 00:00:00 2001 From: raymonder jin Date: Fri, 25 Jun 2021 13:22:01 +0800 Subject: [PATCH 561/912] Fix insufficient slice check (#2755) --- tree.go | 2 +- tree_test.go | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 5f35fff..4b5656f 100644 --- a/tree.go +++ b/tree.go @@ -459,7 +459,7 @@ walk: // Outer loop for walking the tree } // Save param value - if params != nil { + if params != nil && cap(*params) > 0 { if value.params == nil { value.params = params } diff --git a/tree_test.go b/tree_test.go index 298c5ed..7459317 100644 --- a/tree_test.go +++ b/tree_test.go @@ -717,6 +717,19 @@ func TestTreeInvalidNodeType(t *testing.T) { } } +func TestTreeInvalidParamsType(t *testing.T) { + tree := &node{} + tree.wildChild = true + tree.children = append(tree.children, &node{}) + tree.children[0].nType = 2 + + // set invalid Params type + params := make(Params, 0, 0) + + // try to trigger slice bounds out of range with capacity 0 + tree.getValue("/test", ¶ms, false) +} + func TestTreeWildcardConflictEx(t *testing.T) { conflicts := [...]struct { route string From e3ee01d1850b018b24ac215a3bb8de3d79e2604b Mon Sep 17 00:00:00 2001 From: ziheng Date: Mon, 28 Jun 2021 22:05:29 +0800 Subject: [PATCH 562/912] improve sliceValidateError.Error performance using switch and strings.Builder (#2765) fix missing nil pointer check use simpler switch case add missing tests use for-loop instead of range add benchmark test codes --- binding/default_validator.go | 24 +++++++++++++++------ binding/default_validator_benchmark_test.go | 20 +++++++++++++++++ binding/default_validator_test.go | 20 +++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 binding/default_validator_benchmark_test.go diff --git a/binding/default_validator.go b/binding/default_validator.go index ee69329..87fc4c6 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -20,15 +20,27 @@ type defaultValidator struct { type sliceValidateError []error +// Error concatenates all error elements in sliceValidateError into a single string separated by \n. func (err sliceValidateError) Error() string { - var errMsgs []string - for i, e := range err { - if e == nil { - continue + n := len(err) + switch n { + case 0: + return "" + default: + var b strings.Builder + if err[0] != nil { + fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error()) + } + if n > 1 { + for i := 1; i < n; i++ { + if err[i] != nil { + b.WriteString("\n") + fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error()) + } + } } - errMsgs = append(errMsgs, fmt.Sprintf("[%d]: %s", i, e.Error())) + return b.String() } - return strings.Join(errMsgs, "\n") } var _ StructValidator = &defaultValidator{} diff --git a/binding/default_validator_benchmark_test.go b/binding/default_validator_benchmark_test.go new file mode 100644 index 0000000..839cf71 --- /dev/null +++ b/binding/default_validator_benchmark_test.go @@ -0,0 +1,20 @@ +package binding + +import ( + "errors" + "strconv" + "testing" +) + +func BenchmarkSliceValidateError(b *testing.B) { + const size int = 100 + for i := 0; i < b.N; i++ { + e := make(sliceValidateError, size) + for j := 0; j < size; j++ { + e[j] = errors.New(strconv.Itoa(j)) + } + if len(e.Error()) == 0 { + b.Errorf("error") + } + } +} diff --git a/binding/default_validator_test.go b/binding/default_validator_test.go index e9c6de4..e9debe5 100644 --- a/binding/default_validator_test.go +++ b/binding/default_validator_test.go @@ -16,6 +16,26 @@ func TestSliceValidateError(t *testing.T) { want string }{ {"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"}, + {"has zero elements", sliceValidateError{}, ""}, + {"has one element", sliceValidateError{errors.New("test one error")}, "[0]: test one error"}, + {"has two elements", + sliceValidateError{ + errors.New("first error"), + errors.New("second error"), + }, + "[0]: first error\n[1]: second error", + }, + {"has many elements", + sliceValidateError{ + errors.New("first error"), + errors.New("second error"), + nil, + nil, + nil, + errors.New("last error"), + }, + "[0]: first error\n[1]: second error\n[5]: last error", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 690aa2b1b9de7fe61dc15295210f64583dd3b90e Mon Sep 17 00:00:00 2001 From: voidman Date: Wed, 30 Jun 2021 00:53:56 +0800 Subject: [PATCH 563/912] feat(binding): support custom struct tag (#2720) * feat(binding): support custom struct tag Add function `binding.MapFormWithTag` (#2719) * doc: add 'bind form-data with custom struct tag' Add 'Bind form-data request with custom struct and custom tag' section (#2719) * test(binding): add test for MapFromWithTag --- README.md | 55 ++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 4 +++ binding/form_mapping_test.go | 9 ++++++ 3 files changed, 68 insertions(+) diff --git a/README.md b/README.md index ef37fd2..ef80117 100644 --- a/README.md +++ b/README.md @@ -2021,6 +2021,61 @@ enough to call binding at once. can be called by `c.ShouldBind()` multiple times without any damage to performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). +### Bind form-data request with custom struct and custom tag + +```go +const ( + customerTag = "url" + defaultMemory = 32 << 20 +) + +type customerBinding struct {} + +func (customerBinding) Name() string { + return "form" +} + +func (customerBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseForm(); err != nil { + return err + } + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } + if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { + return err + } + return validate(obj) +} + +func validate(obj interface{}) error { + if binding.Validator == nil { + return nil + } + return binding.Validator.ValidateStruct(obj) +} + +// Now we can do this!!! +// FormA is a external type that we can't modify it's tag +type FormA struct { + FieldA string `url:"field_a"` +} + +func ListHandler(s *Service) func(ctx *gin.Context) { + return func(ctx *gin.Context) { + var urlBinding = customerBinding{} + var opt FormA + err := ctx.MustBindWith(&opt, urlBinding) + if err != nil { + ... + } + ... + } +} +``` + ### http2 server push http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 421c0f7..cb66dd4 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -34,6 +34,10 @@ func mapForm(ptr interface{}, form map[string][]string) error { return mapFormByTag(ptr, form, "form") } +func MapFormWithTag(ptr interface{}, form map[string][]string, tag string) error { + return mapFormByTag(ptr, form, tag) +} + var emptyField = reflect.StructField{} func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 2675d46..608594e 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -145,6 +145,15 @@ func TestMappingForm(t *testing.T) { assert.Equal(t, int(6), s.F) } +func TestMapFormWithTag(t *testing.T) { + var s struct { + F int `externalTag:"field"` + } + err := MapFormWithTag(&s, map[string][]string{"field": {"6"}}, "externalTag") + assert.NoError(t, err) + assert.Equal(t, int(6), s.F) +} + func TestMappingTime(t *testing.T) { var s struct { Time time.Time From 372cc4a0100efd3dbd80193b74ee65752d139ea8 Mon Sep 17 00:00:00 2001 From: Lanco <35420416+lancoLiu@users.noreply.github.com> Date: Fri, 2 Jul 2021 09:58:43 +0800 Subject: [PATCH 564/912] Fix typo (#2772) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index ede16fd..8aabf7a 100644 --- a/context.go +++ b/context.go @@ -732,7 +732,7 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e // If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy, // the remote IP (coming form Request.RemoteAddr) is returned. func (c *Context) ClientIP() string { - // Check if we're running on a tursted platform + // Check if we're running on a trusted platform switch c.engine.TrustedPlatform { case PlatformGoogleAppEngine: if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { From 9c27053243cb24ecc90d01c8ff379bd98fed9c8e Mon Sep 17 00:00:00 2001 From: Lanco <35420416+lancoLiu@users.noreply.github.com> Date: Sun, 4 Jul 2021 10:37:13 +0800 Subject: [PATCH 565/912] byte alignment (#2774) --- gin.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/gin.go b/gin.go index fd4fa51..7bdb685 100644 --- a/gin.go +++ b/gin.go @@ -99,6 +99,23 @@ type Engine struct { // `(*gin.Context).Request.RemoteAddr`. ForwardedByClientIP bool + // DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.GoogleAppEngine` INSTEAD + // #726 #755 If enabled, it will trust some headers starting with + // 'X-AppEngine...' for better integration with that PaaS. + AppEngine bool + + // If enabled, the url.RawPath will be used to find parameters. + UseRawPath bool + + // If true, the path value will be unescaped. + // If UseRawPath is false (by default), the UnescapePathValues effectively is true, + // as url.Path gonna be used, which is already unescaped. + UnescapePathValues bool + + // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. + // See the PR #1817 and issue #1644 + RemoveExtraSlash bool + // List of headers used to obtain the client IP when // `(*gin.Engine).ForwardedByClientIP` is `true` and // `(*gin.Context).Request.RemoteAddr` is matched by at least one of the @@ -115,27 +132,10 @@ type Engine struct { // that platform, for example to determine the client IP TrustedPlatform string - // DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.GoogleAppEngine` INSTEAD - // #726 #755 If enabled, it will trust some headers starting with - // 'X-AppEngine...' for better integration with that PaaS. - AppEngine bool - - // If enabled, the url.RawPath will be used to find parameters. - UseRawPath bool - - // If true, the path value will be unescaped. - // If UseRawPath is false (by default), the UnescapePathValues effectively is true, - // as url.Path gonna be used, which is already unescaped. - UnescapePathValues bool - // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm // method call. MaxMultipartMemory int64 - // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. - // See the PR #1817 and issue #1644 - RemoveExtraSlash bool - delims render.Delims secureJSONPrefix string HTMLRender render.HTMLRender From 9d2883ef47c726e9eb466cdd75123c0e4472ac65 Mon Sep 17 00:00:00 2001 From: Helios <674876158@qq.com> Date: Tue, 6 Jul 2021 16:36:32 +0800 Subject: [PATCH 566/912] update the version of validator in the comment (#2780) Co-authored-by: shangyilong --- binding/binding.go | 4 ++-- binding/binding_nomsgpack.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 5caeb58..deb7166 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -49,7 +49,7 @@ type BindingUri interface { // StructValidator is the minimal interface which needs to be implemented in // order for it to be used as the validator engine for ensuring the correctness // of the request. Gin provides a default implementation for this using -// https://github.com/go-playground/validator/tree/v8.18.2. +// https://github.com/go-playground/validator/tree/v10.6.1. 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 a slice|array, the validation should be performed travel on every element. @@ -65,7 +65,7 @@ type StructValidator interface { } // Validator is the default validator which implements the StructValidator -// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 +// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1 // under the hood. var Validator StructValidator = &defaultValidator{} diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index 9afa3dc..2342447 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -47,7 +47,7 @@ type BindingUri interface { // StructValidator is the minimal interface which needs to be implemented in // order for it to be used as the validator engine for ensuring the correctness // of the request. Gin provides a default implementation for this using -// https://github.com/go-playground/validator/tree/v8.18.2. +// https://github.com/go-playground/validator/tree/v10.6.1. 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. @@ -62,7 +62,7 @@ type StructValidator interface { } // Validator is the default validator which implements the StructValidator -// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 +// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1 // under the hood. var Validator StructValidator = &defaultValidator{} From c7a28f85320125709e47c592a92421a4d6f192a7 Mon Sep 17 00:00:00 2001 From: ziheng Date: Tue, 6 Jul 2021 16:37:14 +0800 Subject: [PATCH 567/912] use bit shift operation instead of division (#2776) --- context.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 8aabf7a..ecf74ba 100644 --- a/context.go +++ b/context.go @@ -40,7 +40,8 @@ const ( // BodyBytesKey indicates a default body bytes key. const BodyBytesKey = "_gin-gonic/gin/bodybyteskey" -const abortIndex int8 = math.MaxInt8 / 2 +// abortIndex represents a typical value used in abort functions. +const abortIndex int8 = math.MaxInt8 >> 1 // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. From 3116a2d7a1bf5ed07c50a2b0851a4bed15b3e3b4 Mon Sep 17 00:00:00 2001 From: ziheng Date: Fri, 9 Jul 2021 10:30:44 +0800 Subject: [PATCH 568/912] use std http method constant instead of raw string (#2782) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 7bdb685..6ab2be6 100644 --- a/gin.go +++ b/gin.go @@ -539,7 +539,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { c.writermem.WriteHeaderNow() return } - if httpMethod != "CONNECT" && rPath != "/" { + if httpMethod != http.MethodConnect && rPath != "/" { if value.tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return From f96678cb6b897f92921a2a52ae1c9f0248346a12 Mon Sep 17 00:00:00 2001 From: Lanco <35420416+lancoLiu@users.noreply.github.com> Date: Sun, 11 Jul 2021 14:38:45 +0800 Subject: [PATCH 569/912] use assert1 func (#2783) --- routergroup.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/routergroup.go b/routergroup.go index 6f14bf5..bb24bd5 100644 --- a/routergroup.go +++ b/routergroup.go @@ -214,9 +214,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) - if finalSize >= int(abortIndex) { - panic("too many handlers") - } + assert1(finalSize < int(abortIndex), "too many handlers") mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) From caf2802593277033683c4e8cb5f22c81cc35eae8 Mon Sep 17 00:00:00 2001 From: ziheng Date: Tue, 13 Jul 2021 09:44:19 +0800 Subject: [PATCH 570/912] Improve router group tests (#2787) --- routergroup_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/routergroup_test.go b/routergroup_test.go index 0e49d65..d6d8b45 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -112,15 +112,19 @@ func TestRouterGroupInvalidStaticFile(t *testing.T) { } func TestRouterGroupTooManyHandlers(t *testing.T) { + const ( + panicValue = "too many handlers" + maximumCnt = abortIndex + ) router := New() - handlers1 := make([]HandlerFunc, 40) + handlers1 := make([]HandlerFunc, maximumCnt-1) router.Use(handlers1...) - handlers2 := make([]HandlerFunc, 26) - assert.Panics(t, func() { + handlers2 := make([]HandlerFunc, maximumCnt+1) + assert.PanicsWithValue(t, panicValue, func() { router.Use(handlers2...) }) - assert.Panics(t, func() { + assert.PanicsWithValue(t, panicValue, func() { router.GET("/", handlers2...) }) } From d4ca9a0fb1211108f0a893634ee01b0006890c16 Mon Sep 17 00:00:00 2001 From: qm012 <67568757+qm012@users.noreply.github.com> Date: Thu, 22 Jul 2021 17:58:15 -0500 Subject: [PATCH 571/912] fix #2762 (#2767) --- gin_integration_test.go | 78 ++++++++++++++++++++++++++++++++++-- tree.go | 88 +++++++++++++++++++++++++++++------------ tree_test.go | 36 +++++++++++++++++ 3 files changed, 172 insertions(+), 30 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index 2eb2d52..ed7196f 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -22,7 +22,15 @@ import ( "github.com/stretchr/testify/assert" ) -func testRequest(t *testing.T, url string) { +// params[0]=url example:http://127.0.0.1:8080/index (cannot be empty) +// params[1]=response status (custom compare status) default:"200 OK" +// params[2]=response body (custom compare content) default:"it worked" +func testRequest(t *testing.T, params ...string) { + + if len(params) == 0 { + t.Fatal("url cannot be empty") + } + tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, @@ -30,14 +38,27 @@ func testRequest(t *testing.T, url string) { } client := &http.Client{Transport: tr} - resp, err := client.Get(url) + resp, err := client.Get(params[0]) assert.NoError(t, err) defer resp.Body.Close() body, ioerr := ioutil.ReadAll(resp.Body) assert.NoError(t, ioerr) - assert.Equal(t, "it worked", string(body), "resp body should match") - assert.Equal(t, "200 OK", resp.Status, "should get a 200") + + var responseStatus = "200 OK" + if len(params) > 1 && params[1] != "" { + responseStatus = params[1] + } + + var responseBody = "it worked" + if len(params) > 2 && params[2] != "" { + responseBody = params[2] + } + + assert.Equal(t, responseStatus, resp.Status, "should get a "+responseStatus) + if responseStatus == "200 OK" { + assert.Equal(t, responseBody, string(body), "resp body should match") + } } func TestRunEmpty(t *testing.T) { @@ -373,3 +394,52 @@ func testGetRequestHandler(t *testing.T, h http.Handler, url string) { assert.Equal(t, "it worked", w.Body.String(), "resp body should match") assert.Equal(t, 200, w.Code, "should get a 200") } + +func TestTreeRunDynamicRouting(t *testing.T) { + router := New() + router.GET("/aa/*xx", func(c *Context) { c.String(http.StatusOK, "/aa/*xx") }) + router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") }) + router.GET("/", func(c *Context) { c.String(http.StatusOK, "home") }) + router.GET("/:cc", func(c *Context) { c.String(http.StatusOK, "/:cc") }) + router.GET("/:cc/cc", func(c *Context) { c.String(http.StatusOK, "/:cc/cc") }) + router.GET("/:cc/:dd/ee", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/ee") }) + router.GET("/:cc/:dd/:ee/ff", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/ff") }) + router.GET("/:cc/:dd/:ee/:ff/gg", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/gg") }) + router.GET("/:cc/:dd/:ee/:ff/:gg/hh", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/:gg/hh") }) + router.GET("/get/test/abc/", func(c *Context) { c.String(http.StatusOK, "/get/test/abc/") }) + router.GET("/get/:param/abc/", func(c *Context) { c.String(http.StatusOK, "/get/:param/abc/") }) + router.GET("/something/:paramname/thirdthing", func(c *Context) { c.String(http.StatusOK, "/something/:paramname/thirdthing") }) + router.GET("/something/secondthing/test", func(c *Context) { c.String(http.StatusOK, "/something/secondthing/test") }) + + ts := httptest.NewServer(router) + defer ts.Close() + + testRequest(t, ts.URL+"/", "", "home") + testRequest(t, ts.URL+"/aa/aa", "", "/aa/*xx") + testRequest(t, ts.URL+"/ab/ab", "", "/ab/*xx") + testRequest(t, ts.URL+"/all", "", "/:cc") + testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc") + testRequest(t, ts.URL+"/a/cc", "", "/:cc/cc") + testRequest(t, ts.URL+"/c/d/ee", "", "/:cc/:dd/ee") + testRequest(t, ts.URL+"/c/d/e/ff", "", "/:cc/:dd/:ee/ff") + testRequest(t, ts.URL+"/c/d/e/f/gg", "", "/:cc/:dd/:ee/:ff/gg") + testRequest(t, ts.URL+"/c/d/e/f/g/hh", "", "/:cc/:dd/:ee/:ff/:gg/hh") + testRequest(t, ts.URL+"/a", "", "/:cc") + testRequest(t, ts.URL+"/get/test/abc/", "", "/get/test/abc/") + testRequest(t, ts.URL+"/get/te/abc/", "", "/get/:param/abc/") + testRequest(t, ts.URL+"/get/xx/abc/", "", "/get/:param/abc/") + testRequest(t, ts.URL+"/get/tt/abc/", "", "/get/:param/abc/") + testRequest(t, ts.URL+"/get/a/abc/", "", "/get/:param/abc/") + testRequest(t, ts.URL+"/get/t/abc/", "", "/get/:param/abc/") + testRequest(t, ts.URL+"/get/aa/abc/", "", "/get/:param/abc/") + testRequest(t, ts.URL+"/get/abas/abc/", "", "/get/:param/abc/") + testRequest(t, ts.URL+"/something/secondthing/test", "", "/something/secondthing/test") + testRequest(t, ts.URL+"/something/abcdad/thirdthing", "", "/something/:paramname/thirdthing") + testRequest(t, ts.URL+"/something/se/thirdthing", "", "/something/:paramname/thirdthing") + testRequest(t, ts.URL+"/something/s/thirdthing", "", "/something/:paramname/thirdthing") + testRequest(t, ts.URL+"/something/secondthing/thirdthing", "", "/something/:paramname/thirdthing") + // 404 not found + testRequest(t, ts.URL+"/a/dd", "404 Not Found") + testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found") + testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found") +} diff --git a/tree.go b/tree.go index 4b5656f..2e46b8e 100644 --- a/tree.go +++ b/tree.go @@ -118,11 +118,6 @@ type node struct { fullPath string } -type skip struct { - path string - paramNode *node -} - // Increments priority of the given child and reorders if necessary func (n *node) incrementChildPrio(pos int) int { cs := n.children @@ -405,7 +400,23 @@ type nodeValue struct { // made if a handle exists with an extra (without the) trailing slash for the // given path. func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) { - var skipped *skip + // path: /abc/123/def + // level 1 router:abc + // level 2 router:123 + // level 3 router:def + var ( + skippedPath string + latestNode = n // not found `level 2 router` use latestNode + + // match '/' count + // matchNum < 1: `level 2 router` not found,the current node needs to be equal to latestNode + // matchNum >= 1: `level (2 or 3 or 4 or ...) router`: Normal handling + matchNum int // each match will accumulate + ) + //if path == "/", no need to look for tree node + if len(path) == 1 { + matchNum = 1 + } walk: // Outer loop for walking the tree for { @@ -418,32 +429,41 @@ walk: // Outer loop for walking the tree idxc := path[0] for i, c := range []byte(n.indices) { if c == idxc { - if strings.HasPrefix(n.children[len(n.children)-1].path, ":") { - skipped = &skip{ - path: prefix + path, - paramNode: &node{ - path: n.path, - wildChild: n.wildChild, - nType: n.nType, - priority: n.priority, - children: n.children, - handlers: n.handlers, - fullPath: n.fullPath, - }, + // strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild + if n.wildChild { + skippedPath = prefix + path + latestNode = &node{ + path: n.path, + wildChild: n.wildChild, + nType: n.nType, + priority: n.priority, + children: n.children, + handlers: n.handlers, + fullPath: n.fullPath, } } n = n.children[i] + + // match '/', If this condition is matched, the next route is found + if (len(n.fullPath) != 0 && n.wildChild) || path[len(path)-1] == '/' { + matchNum++ + } continue walk } } + // level 2 router not found,the current node needs to be equal to latestNode + if matchNum < 1 { + n = latestNode + } + // If there is no wildcard pattern, recommend a redirection if !n.wildChild { // Nothing found. // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. - value.tsr = (path == "/" && n.handlers != nil) + value.tsr = path == "/" && n.handlers != nil return } @@ -483,6 +503,18 @@ walk: // Outer loop for walking the tree if len(n.children) > 0 { path = path[end:] n = n.children[0] + // next node,the latestNode needs to be equal to currentNode and handle next router + latestNode = n + // not found router in (level 1 router and handle next node),skippedPath cannot execute + // example: + // * /:cc/cc + // call /a/cc expectations:match/200 Actual:match/200 + // call /a/dd expectations:unmatch/404 Actual: panic + // call /addr/dd/aa expectations:unmatch/404 Actual: panic + // skippedPath: It can only be executed if the secondary route is not found + // matchNum: Go to the next level of routing tree node search,need add matchNum + skippedPath = "" + matchNum++ continue walk } @@ -535,6 +567,10 @@ walk: // Outer loop for walking the tree } if path == prefix { + // level 2 router not found and latestNode.wildChild is true + if matchNum < 1 && latestNode.wildChild { + n = latestNode.children[len(latestNode.children)-1] + } // We should have reached the node containing the handle. // Check if this node has a handle registered. if value.handlers = n.handlers; value.handlers != nil { @@ -564,18 +600,18 @@ walk: // Outer loop for walking the tree return } - if path != "/" && skipped != nil && strings.HasSuffix(skipped.path, path) { - path = skipped.path - n = skipped.paramNode - skipped = nil + // path != "/" && skippedPath != "" + if len(path) != 1 && len(skippedPath) > 0 && strings.HasSuffix(skippedPath, path) { + path = skippedPath + n = latestNode + skippedPath = "" continue walk } // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path - value.tsr = (path == "/") || - (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && - path == prefix[:len(prefix)-1] && n.handlers != nil) + value.tsr = path == "/" || + (len(prefix) == len(path)+1 && n.handlers != nil) return } } diff --git a/tree_test.go b/tree_test.go index 7459317..91213ee 100644 --- a/tree_test.go +++ b/tree_test.go @@ -154,6 +154,18 @@ func TestTreeWildcard(t *testing.T) { "/info/:user/public", "/info/:user/project/:project", "/info/:user/project/golang", + "/aa/*xx", + "/ab/*xx", + "/:cc", + "/:cc/cc", + "/:cc/:dd/ee", + "/:cc/:dd/:ee/ff", + "/:cc/:dd/:ee/:ff/gg", + "/:cc/:dd/:ee/:ff/:gg/hh", + "/get/test/abc/", + "/get/:param/abc/", + "/something/:paramname/thirdthing", + "/something/secondthing/test", } for _, route := range routes { tree.addRoute(route, fakeHandler(route)) @@ -186,6 +198,30 @@ func TestTreeWildcard(t *testing.T) { {"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}}, {"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}}, {"/info/gordon/project/golang", false, "/info/:user/project/golang", Params{Param{Key: "user", Value: "gordon"}}}, + {"/aa/aa", false, "/aa/*xx", Params{Param{Key: "xx", Value: "/aa"}}}, + {"/ab/ab", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/ab"}}}, + {"/a", false, "/:cc", Params{Param{Key: "cc", Value: "a"}}}, + // * level 1 router match param will be Intercept first + // new PR handle (/all /all/cc /a/cc) + {"/all", false, "/:cc", Params{Param{Key: "cc", Value: "ll"}}}, + {"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ll"}}}, + {"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: ""}}}, + {"/get/test/abc/", false, "/get/test/abc/", nil}, + {"/get/te/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "te"}}}, + {"/get/xx/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "xx"}}}, + {"/get/tt/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "tt"}}}, + {"/get/a/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "a"}}}, + {"/get/t/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "t"}}}, + {"/get/aa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "aa"}}}, + {"/get/abas/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "abas"}}}, + {"/something/secondthing/test", false, "/something/secondthing/test", nil}, + {"/something/abcdad/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "abcdad"}}}, + {"/something/se/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "se"}}}, + {"/something/s/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "s"}}}, + {"/c/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}}}, + {"/c/d/e/ff", false, "/:cc/:dd/:ee/ff", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}}}, + {"/c/d/e/f/gg", false, "/:cc/:dd/:ee/:ff/gg", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}}}, + {"/c/d/e/f/g/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}, Param{Key: "gg", Value: "g"}}}, }) checkPriorities(t, tree) From 0a55865c3fc4c2b30dce86dfa9cff7654ba9bc74 Mon Sep 17 00:00:00 2001 From: qm012 <67568757+qm012@users.noreply.github.com> Date: Sun, 25 Jul 2021 21:07:54 -0500 Subject: [PATCH 572/912] fix #2786 (#2796) * update match rule * add comments --- gin_integration_test.go | 82 +++++++++++++++++++++++++++++++++++++ tree.go | 67 +++++++++++++----------------- tree_test.go | 91 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 197 insertions(+), 43 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index ed7196f..094c46e 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -410,6 +410,25 @@ func TestTreeRunDynamicRouting(t *testing.T) { router.GET("/get/:param/abc/", func(c *Context) { c.String(http.StatusOK, "/get/:param/abc/") }) router.GET("/something/:paramname/thirdthing", func(c *Context) { c.String(http.StatusOK, "/something/:paramname/thirdthing") }) router.GET("/something/secondthing/test", func(c *Context) { c.String(http.StatusOK, "/something/secondthing/test") }) + router.GET("/get/abc", func(c *Context) { c.String(http.StatusOK, "/get/abc") }) + router.GET("/get/:param", func(c *Context) { c.String(http.StatusOK, "/get/:param") }) + router.GET("/get/abc/123abc", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc") }) + router.GET("/get/abc/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/:param") }) + router.GET("/get/abc/123abc/xxx8", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8") }) + router.GET("/get/abc/123abc/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/:param") }) + router.GET("/get/abc/123abc/xxx8/1234", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234") }) + router.GET("/get/abc/123abc/xxx8/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/:param") }) + router.GET("/get/abc/123abc/xxx8/1234/ffas", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/ffas") }) + router.GET("/get/abc/123abc/xxx8/1234/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/:param") }) + router.GET("/get/abc/123abc/xxx8/1234/kkdd/12c", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/kkdd/12c") }) + router.GET("/get/abc/123abc/xxx8/1234/kkdd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/kkdd/:param") }) + router.GET("/get/abc/:param/test", func(c *Context) { c.String(http.StatusOK, "/get/abc/:param/test") }) + router.GET("/get/abc/123abd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abd/:param") }) + router.GET("/get/abc/123abddd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abddd/:param") }) + router.GET("/get/abc/123/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123/:param") }) + router.GET("/get/abc/123abg/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abg/:param") }) + router.GET("/get/abc/123abf/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abf/:param") }) + router.GET("/get/abc/123abfff/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abfff/:param") }) ts := httptest.NewServer(router) defer ts.Close() @@ -424,8 +443,26 @@ func TestTreeRunDynamicRouting(t *testing.T) { testRequest(t, ts.URL+"/c/d/e/ff", "", "/:cc/:dd/:ee/ff") testRequest(t, ts.URL+"/c/d/e/f/gg", "", "/:cc/:dd/:ee/:ff/gg") testRequest(t, ts.URL+"/c/d/e/f/g/hh", "", "/:cc/:dd/:ee/:ff/:gg/hh") + testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh", "", "/:cc/:dd/:ee/:ff/:gg/hh") testRequest(t, ts.URL+"/a", "", "/:cc") + testRequest(t, ts.URL+"/d", "", "/:cc") + testRequest(t, ts.URL+"/ad", "", "/:cc") + testRequest(t, ts.URL+"/dd", "", "/:cc") + testRequest(t, ts.URL+"/aa", "", "/:cc") + testRequest(t, ts.URL+"/aaa", "", "/:cc") + testRequest(t, ts.URL+"/aaa/cc", "", "/:cc/cc") + testRequest(t, ts.URL+"/ab", "", "/:cc") + testRequest(t, ts.URL+"/abb", "", "/:cc") + testRequest(t, ts.URL+"/abb/cc", "", "/:cc/cc") + testRequest(t, ts.URL+"/dddaa", "", "/:cc") + testRequest(t, ts.URL+"/allxxxx", "", "/:cc") + testRequest(t, ts.URL+"/alldd", "", "/:cc") + testRequest(t, ts.URL+"/cc/cc", "", "/:cc/cc") + testRequest(t, ts.URL+"/ccc/cc", "", "/:cc/cc") + testRequest(t, ts.URL+"/deedwjfs/cc", "", "/:cc/cc") + testRequest(t, ts.URL+"/acllcc/cc", "", "/:cc/cc") testRequest(t, ts.URL+"/get/test/abc/", "", "/get/test/abc/") + testRequest(t, ts.URL+"/get/testaa/abc/", "", "/get/:param/abc/") testRequest(t, ts.URL+"/get/te/abc/", "", "/get/:param/abc/") testRequest(t, ts.URL+"/get/xx/abc/", "", "/get/:param/abc/") testRequest(t, ts.URL+"/get/tt/abc/", "", "/get/:param/abc/") @@ -434,10 +471,55 @@ func TestTreeRunDynamicRouting(t *testing.T) { testRequest(t, ts.URL+"/get/aa/abc/", "", "/get/:param/abc/") testRequest(t, ts.URL+"/get/abas/abc/", "", "/get/:param/abc/") testRequest(t, ts.URL+"/something/secondthing/test", "", "/something/secondthing/test") + testRequest(t, ts.URL+"/something/secondthingaaaa/thirdthing", "", "/something/:paramname/thirdthing") testRequest(t, ts.URL+"/something/abcdad/thirdthing", "", "/something/:paramname/thirdthing") testRequest(t, ts.URL+"/something/se/thirdthing", "", "/something/:paramname/thirdthing") testRequest(t, ts.URL+"/something/s/thirdthing", "", "/something/:paramname/thirdthing") testRequest(t, ts.URL+"/something/secondthing/thirdthing", "", "/something/:paramname/thirdthing") + testRequest(t, ts.URL+"/get/abc", "", "/get/abc") + testRequest(t, ts.URL+"/get/a", "", "/get/:param") + testRequest(t, ts.URL+"/get/abz", "", "/get/:param") + testRequest(t, ts.URL+"/get/12a", "", "/get/:param") + testRequest(t, ts.URL+"/get/abcd", "", "/get/:param") + testRequest(t, ts.URL+"/get/abc/123abc", "", "/get/abc/123abc") + testRequest(t, ts.URL+"/get/abc/12", "", "/get/abc/:param") + testRequest(t, ts.URL+"/get/abc/123ab", "", "/get/abc/:param") + testRequest(t, ts.URL+"/get/abc/xyz", "", "/get/abc/:param") + testRequest(t, ts.URL+"/get/abc/123abcddxx", "", "/get/abc/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8", "", "/get/abc/123abc/xxx8") + testRequest(t, ts.URL+"/get/abc/123abc/x", "", "/get/abc/123abc/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx", "", "/get/abc/123abc/:param") + testRequest(t, ts.URL+"/get/abc/123abc/abc", "", "/get/abc/123abc/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8xxas", "", "/get/abc/123abc/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234", "", "/get/abc/123abc/xxx8/1234") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1", "", "/get/abc/123abc/xxx8/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/123", "", "/get/abc/123abc/xxx8/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/78k", "", "/get/abc/123abc/xxx8/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234xxxd", "", "/get/abc/123abc/xxx8/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffas", "", "/get/abc/123abc/xxx8/1234/ffas") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/f", "", "/get/abc/123abc/xxx8/1234/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffa", "", "/get/abc/123abc/xxx8/1234/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kka", "", "/get/abc/123abc/xxx8/1234/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffas321", "", "/get/abc/123abc/xxx8/1234/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12c", "", "/get/abc/123abc/xxx8/1234/kkdd/12c") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/1", "", "/get/abc/123abc/xxx8/1234/kkdd/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12", "", "/get/abc/123abc/xxx8/1234/kkdd/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12b", "", "/get/abc/123abc/xxx8/1234/kkdd/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/34", "", "/get/abc/123abc/xxx8/1234/kkdd/:param") + testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12c2e3", "", "/get/abc/123abc/xxx8/1234/kkdd/:param") + testRequest(t, ts.URL+"/get/abc/12/test", "", "/get/abc/:param/test") + testRequest(t, ts.URL+"/get/abc/123abdd/test", "", "/get/abc/:param/test") + testRequest(t, ts.URL+"/get/abc/123abdddf/test", "", "/get/abc/:param/test") + testRequest(t, ts.URL+"/get/abc/123ab/test", "", "/get/abc/:param/test") + testRequest(t, ts.URL+"/get/abc/123abgg/test", "", "/get/abc/:param/test") + testRequest(t, ts.URL+"/get/abc/123abff/test", "", "/get/abc/:param/test") + testRequest(t, ts.URL+"/get/abc/123abffff/test", "", "/get/abc/:param/test") + testRequest(t, ts.URL+"/get/abc/123abd/test", "", "/get/abc/123abd/:param") + testRequest(t, ts.URL+"/get/abc/123abddd/test", "", "/get/abc/123abddd/:param") + testRequest(t, ts.URL+"/get/abc/123/test22", "", "/get/abc/123/:param") + testRequest(t, ts.URL+"/get/abc/123abg/test", "", "/get/abc/123abg/:param") + testRequest(t, ts.URL+"/get/abc/123abf/testss", "", "/get/abc/123abf/:param") + testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param") // 404 not found testRequest(t, ts.URL+"/a/dd", "404 Not Found") testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found") diff --git a/tree.go b/tree.go index 2e46b8e..eb54959 100644 --- a/tree.go +++ b/tree.go @@ -400,23 +400,10 @@ type nodeValue struct { // made if a handle exists with an extra (without the) trailing slash for the // given path. func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) { - // path: /abc/123/def - // level 1 router:abc - // level 2 router:123 - // level 3 router:def var ( skippedPath string - latestNode = n // not found `level 2 router` use latestNode - - // match '/' count - // matchNum < 1: `level 2 router` not found,the current node needs to be equal to latestNode - // matchNum >= 1: `level (2 or 3 or 4 or ...) router`: Normal handling - matchNum int // each match will accumulate + latestNode = n // Caching the latest node ) - //if path == "/", no need to look for tree node - if len(path) == 1 { - matchNum = 1 - } walk: // Outer loop for walking the tree for { @@ -444,17 +431,13 @@ walk: // Outer loop for walking the tree } n = n.children[i] - - // match '/', If this condition is matched, the next route is found - if (len(n.fullPath) != 0 && n.wildChild) || path[len(path)-1] == '/' { - matchNum++ - } continue walk } } - - // level 2 router not found,the current node needs to be equal to latestNode - if matchNum < 1 { + // If the path at the end of the loop is not equal to '/' and the current node has no child nodes + // the current node needs to be equal to the latest matching node + matched := path != "/" && !n.wildChild + if matched { n = latestNode } @@ -472,6 +455,16 @@ walk: // Outer loop for walking the tree switch n.nType { case param: + // fix truncate the parameter + // tree_test.go line: 204 + if matched { + path = prefix + path + // The saved path is used after the prefix route is intercepted by matching + if n.indices == "/" { + path = skippedPath[1:] + } + } + // Find param end (either '/' or path end) end := 0 for end < len(path) && path[end] != '/' { @@ -503,18 +496,6 @@ walk: // Outer loop for walking the tree if len(n.children) > 0 { path = path[end:] n = n.children[0] - // next node,the latestNode needs to be equal to currentNode and handle next router - latestNode = n - // not found router in (level 1 router and handle next node),skippedPath cannot execute - // example: - // * /:cc/cc - // call /a/cc expectations:match/200 Actual:match/200 - // call /a/dd expectations:unmatch/404 Actual: panic - // call /addr/dd/aa expectations:unmatch/404 Actual: panic - // skippedPath: It can only be executed if the secondary route is not found - // matchNum: Go to the next level of routing tree node search,need add matchNum - skippedPath = "" - matchNum++ continue walk } @@ -567,8 +548,9 @@ walk: // Outer loop for walking the tree } if path == prefix { - // level 2 router not found and latestNode.wildChild is true - if matchNum < 1 && latestNode.wildChild { + // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node + // the current node needs to be equal to the latest matching node + if latestNode.wildChild && n.handlers == nil && path != "/" { n = latestNode.children[len(latestNode.children)-1] } // We should have reached the node containing the handle. @@ -600,10 +582,17 @@ walk: // Outer loop for walking the tree return } - // path != "/" && skippedPath != "" - if len(path) != 1 && len(skippedPath) > 0 && strings.HasSuffix(skippedPath, path) { + if path != "/" && len(skippedPath) > 0 && strings.HasSuffix(skippedPath, path) { path = skippedPath - n = latestNode + // Reduce the number of cycles + n, latestNode = latestNode, n + // skippedPath cannot execute + // example: + // * /:cc/cc + // call /a/cc expectations:match/200 Actual:match/200 + // call /a/dd expectations:unmatch/404 Actual: panic + // call /addr/dd/aa expectations:unmatch/404 Actual: panic + // skippedPath: It can only be executed if the secondary route is not found skippedPath = "" continue walk } diff --git a/tree_test.go b/tree_test.go index 91213ee..ea13c30 100644 --- a/tree_test.go +++ b/tree_test.go @@ -166,6 +166,25 @@ func TestTreeWildcard(t *testing.T) { "/get/:param/abc/", "/something/:paramname/thirdthing", "/something/secondthing/test", + "/get/abc", + "/get/:param", + "/get/abc/123abc", + "/get/abc/:param", + "/get/abc/123abc/xxx8", + "/get/abc/123abc/:param", + "/get/abc/123abc/xxx8/1234", + "/get/abc/123abc/xxx8/:param", + "/get/abc/123abc/xxx8/1234/ffas", + "/get/abc/123abc/xxx8/1234/:param", + "/get/abc/123abc/xxx8/1234/kkdd/12c", + "/get/abc/123abc/xxx8/1234/kkdd/:param", + "/get/abc/:param/test", + "/get/abc/123abd/:param", + "/get/abc/123abddd/:param", + "/get/abc/123/:param", + "/get/abc/123abg/:param", + "/get/abc/123abf/:param", + "/get/abc/123abfff/:param", } for _, route := range routes { tree.addRoute(route, fakeHandler(route)) @@ -201,13 +220,31 @@ func TestTreeWildcard(t *testing.T) { {"/aa/aa", false, "/aa/*xx", Params{Param{Key: "xx", Value: "/aa"}}}, {"/ab/ab", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/ab"}}}, {"/a", false, "/:cc", Params{Param{Key: "cc", Value: "a"}}}, - // * level 1 router match param will be Intercept first + // * Error with argument being intercepted // new PR handle (/all /all/cc /a/cc) - {"/all", false, "/:cc", Params{Param{Key: "cc", Value: "ll"}}}, - {"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ll"}}}, - {"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: ""}}}, + // fix PR: https://github.com/gin-gonic/gin/pull/2796 + {"/all", false, "/:cc", Params{Param{Key: "cc", Value: "all"}}}, + {"/d", false, "/:cc", Params{Param{Key: "cc", Value: "d"}}}, + {"/ad", false, "/:cc", Params{Param{Key: "cc", Value: "ad"}}}, + {"/dd", false, "/:cc", Params{Param{Key: "cc", Value: "dd"}}}, + {"/dddaa", false, "/:cc", Params{Param{Key: "cc", Value: "dddaa"}}}, + {"/aa", false, "/:cc", Params{Param{Key: "cc", Value: "aa"}}}, + {"/aaa", false, "/:cc", Params{Param{Key: "cc", Value: "aaa"}}}, + {"/aaa/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "aaa"}}}, + {"/ab", false, "/:cc", Params{Param{Key: "cc", Value: "ab"}}}, + {"/abb", false, "/:cc", Params{Param{Key: "cc", Value: "abb"}}}, + {"/abb/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "abb"}}}, + {"/allxxxx", false, "/:cc", Params{Param{Key: "cc", Value: "allxxxx"}}}, + {"/alldd", false, "/:cc", Params{Param{Key: "cc", Value: "alldd"}}}, + {"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "all"}}}, + {"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "a"}}}, + {"/cc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "cc"}}}, + {"/ccc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ccc"}}}, + {"/deedwjfs/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "deedwjfs"}}}, + {"/acllcc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "acllcc"}}}, {"/get/test/abc/", false, "/get/test/abc/", nil}, {"/get/te/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "te"}}}, + {"/get/testaa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "testaa"}}}, {"/get/xx/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "xx"}}}, {"/get/tt/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "tt"}}}, {"/get/a/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "a"}}}, @@ -216,12 +253,58 @@ func TestTreeWildcard(t *testing.T) { {"/get/abas/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "abas"}}}, {"/something/secondthing/test", false, "/something/secondthing/test", nil}, {"/something/abcdad/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "abcdad"}}}, + {"/something/secondthingaaaa/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "secondthingaaaa"}}}, {"/something/se/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "se"}}}, {"/something/s/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "s"}}}, {"/c/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}}}, {"/c/d/e/ff", false, "/:cc/:dd/:ee/ff", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}}}, {"/c/d/e/f/gg", false, "/:cc/:dd/:ee/:ff/gg", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}}}, {"/c/d/e/f/g/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}, Param{Key: "gg", Value: "g"}}}, + {"/cc/dd/ee/ff/gg/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "cc"}, Param{Key: "dd", Value: "dd"}, Param{Key: "ee", Value: "ee"}, Param{Key: "ff", Value: "ff"}, Param{Key: "gg", Value: "gg"}}}, + {"/get/abc", false, "/get/abc", nil}, + {"/get/a", false, "/get/:param", Params{Param{Key: "param", Value: "a"}}}, + {"/get/abz", false, "/get/:param", Params{Param{Key: "param", Value: "abz"}}}, + {"/get/12a", false, "/get/:param", Params{Param{Key: "param", Value: "12a"}}}, + {"/get/abcd", false, "/get/:param", Params{Param{Key: "param", Value: "abcd"}}}, + {"/get/abc/123abc", false, "/get/abc/123abc", nil}, + {"/get/abc/12", false, "/get/abc/:param", Params{Param{Key: "param", Value: "12"}}}, + {"/get/abc/123ab", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123ab"}}}, + {"/get/abc/xyz", false, "/get/abc/:param", Params{Param{Key: "param", Value: "xyz"}}}, + {"/get/abc/123abcddxx", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123abcddxx"}}}, + {"/get/abc/123abc/xxx8", false, "/get/abc/123abc/xxx8", nil}, + {"/get/abc/123abc/x", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "x"}}}, + {"/get/abc/123abc/xxx", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx"}}}, + {"/get/abc/123abc/abc", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "abc"}}}, + {"/get/abc/123abc/xxx8xxas", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx8xxas"}}}, + {"/get/abc/123abc/xxx8/1234", false, "/get/abc/123abc/xxx8/1234", nil}, + {"/get/abc/123abc/xxx8/1", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1"}}}, + {"/get/abc/123abc/xxx8/123", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "123"}}}, + {"/get/abc/123abc/xxx8/78k", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "78k"}}}, + {"/get/abc/123abc/xxx8/1234xxxd", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1234xxxd"}}}, + {"/get/abc/123abc/xxx8/1234/ffas", false, "/get/abc/123abc/xxx8/1234/ffas", nil}, + {"/get/abc/123abc/xxx8/1234/f", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "f"}}}, + {"/get/abc/123abc/xxx8/1234/ffa", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffa"}}}, + {"/get/abc/123abc/xxx8/1234/kka", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "kka"}}}, + {"/get/abc/123abc/xxx8/1234/ffas321", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffas321"}}}, + {"/get/abc/123abc/xxx8/1234/kkdd/12c", false, "/get/abc/123abc/xxx8/1234/kkdd/12c", nil}, + {"/get/abc/123abc/xxx8/1234/kkdd/1", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "1"}}}, + {"/get/abc/123abc/xxx8/1234/kkdd/12", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12"}}}, + {"/get/abc/123abc/xxx8/1234/kkdd/12b", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12b"}}}, + {"/get/abc/123abc/xxx8/1234/kkdd/34", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "34"}}}, + {"/get/abc/123abc/xxx8/1234/kkdd/12c2e3", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12c2e3"}}}, + {"/get/abc/12/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "12"}}}, + {"/get/abc/123abdd/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdd"}}}, + {"/get/abc/123abdddf/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdddf"}}}, + {"/get/abc/123ab/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123ab"}}}, + {"/get/abc/123abgg/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abgg"}}}, + {"/get/abc/123abff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abff"}}}, + {"/get/abc/123abffff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abffff"}}}, + {"/get/abc/123abd/test", false, "/get/abc/123abd/:param", Params{Param{Key: "param", Value: "test"}}}, + {"/get/abc/123abddd/test", false, "/get/abc/123abddd/:param", Params{Param{Key: "param", Value: "test"}}}, + {"/get/abc/123/test22", false, "/get/abc/123/:param", Params{Param{Key: "param", Value: "test22"}}}, + {"/get/abc/123abg/test", false, "/get/abc/123abg/:param", Params{Param{Key: "param", Value: "test"}}}, + {"/get/abc/123abf/testss", false, "/get/abc/123abf/:param", Params{Param{Key: "param", Value: "testss"}}}, + {"/get/abc/123abfff/te", false, "/get/abc/123abfff/:param", Params{Param{Key: "param", Value: "te"}}}, }) checkPriorities(t, tree) From 11aa11a65618b6b66bf2b5123e08db6e879c4a1e Mon Sep 17 00:00:00 2001 From: "Eren A. Akyol" Date: Tue, 27 Jul 2021 02:59:53 +0300 Subject: [PATCH 573/912] fix readability in recovery test (#2797) --- recovery_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/recovery_test.go b/recovery_test.go index 6cc2a47..d164bfa 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -92,14 +92,14 @@ func TestPanicWithAbort(t *testing.T) { func TestSource(t *testing.T) { bs := source(nil, 0) - assert.Equal(t, []byte("???"), bs) + assert.Equal(t, dunno, bs) in := [][]byte{ []byte("Hello world."), []byte("Hi, gin.."), } bs = source(in, 10) - assert.Equal(t, []byte("???"), bs) + assert.Equal(t, dunno, bs) bs = source(in, 1) assert.Equal(t, []byte("Hello world."), bs) @@ -107,7 +107,7 @@ func TestSource(t *testing.T) { func TestFunction(t *testing.T) { bs := function(1) - assert.Equal(t, []byte("???"), bs) + assert.Equal(t, dunno, bs) } // TestPanicWithBrokenPipe asserts that recovery specifically handles From 9a575a4c05601954e0848ff8271238dd73485536 Mon Sep 17 00:00:00 2001 From: wei Date: Sun, 1 Aug 2021 00:46:53 +0800 Subject: [PATCH 574/912] fallback Context.Deadline() Context.Done() Context.Err() to Context.Request.Context() (#2769) * fallback Context.Deadline() Context.Done() Context.Err() to Context.Request.Context() * update comments wording --- context.go | 24 +++++++++++++++--------- context_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/context.go b/context.go index ecf74ba..1a95300 100644 --- a/context.go +++ b/context.go @@ -1161,22 +1161,28 @@ func (c *Context) SetAccepted(formats ...string) { /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ -// Deadline always returns that there is no deadline (ok==false), -// maybe you want to use Request.Context().Deadline() instead. +// Deadline returns that there is no deadline (ok==false) when c.Request has no Context. func (c *Context) Deadline() (deadline time.Time, ok bool) { - return + if c.Request == nil || c.Request.Context() == nil { + return + } + return c.Request.Context().Deadline() } -// Done always returns nil (chan which will wait forever), -// if you want to abort your work when the connection was closed -// you should use Request.Context().Done() instead. +// Done returns nil (chan which will wait forever) when c.Request has no Context. func (c *Context) Done() <-chan struct{} { - return nil + if c.Request == nil || c.Request.Context() == nil { + return nil + } + return c.Request.Context().Done() } -// Err always returns nil, maybe you want to use Request.Context().Err() instead. +// Err returns nil when c.Request has no Context. func (c *Context) Err() error { - return nil + if c.Request == nil || c.Request.Context() == nil { + return nil + } + return c.Request.Context().Err() } // Value returns the value associated with this context for key, or nil diff --git a/context_test.go b/context_test.go index 2a4d218..ffbe5cc 100644 --- a/context_test.go +++ b/context_test.go @@ -2058,6 +2058,48 @@ func TestRemoteIPFail(t *testing.T) { assert.False(t, trust) } +func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) { + c := &Context{} + deadline, ok := c.Deadline() + assert.Zero(t, deadline) + assert.False(t, ok) + + c2 := &Context{} + c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + d := time.Now().Add(time.Second) + ctx, cancel := context.WithDeadline(context.Background(), d) + defer cancel() + c2.Request = c2.Request.WithContext(ctx) + deadline, ok = c2.Deadline() + assert.Equal(t, d, deadline) + assert.True(t, ok) +} + +func TestContextWithFallbackDoneFromRequestContext(t *testing.T) { + c := &Context{} + assert.Nil(t, c.Done()) + + c2 := &Context{} + c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + ctx, cancel := context.WithCancel(context.Background()) + c2.Request = c2.Request.WithContext(ctx) + cancel() + assert.NotNil(t, <-c2.Done()) +} + +func TestContextWithFallbackErrFromRequestContext(t *testing.T) { + c := &Context{} + assert.Nil(t, c.Err()) + + c2 := &Context{} + c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + ctx, cancel := context.WithCancel(context.Background()) + c2.Request = c2.Request.WithContext(ctx) + cancel() + + assert.EqualError(t, c2.Err(), context.Canceled.Error()) +} + func TestContextWithFallbackValueFromRequestContext(t *testing.T) { tests := []struct { name string From 6ebb945bd7ac10b0fec87299129b6d9603e1e4ab Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 3 Aug 2021 10:26:26 +0800 Subject: [PATCH 575/912] docs: release v1.7.3 (#2802) * docs: release v1.7.3 Signed-off-by: Bo-Yi Wu * fix: format Signed-off-by: Bo-Yi Wu --- CHANGELOG.md | 6 ++++++ version.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a28edc8..308af74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Gin ChangeLog +## Gin v1.7.3 + +### BUGFIXES + +* fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796) + ## Gin v1.7.2 ### BUGFIXES diff --git a/version.go b/version.go index a80ab69..535bfc8 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.7.2" +const Version = "v1.7.3" From b463b1c2a175cd17365d114e4827aa2d680c3dc9 Mon Sep 17 00:00:00 2001 From: goqihoo Date: Wed, 11 Aug 2021 09:42:25 +0800 Subject: [PATCH 576/912] Update README.md (#2804) 1. c.FullPath() == "/user/:name/*action" get following error: evaluated but not used 2. c.String(http.StatusOK, "The available groups are [...]", name) get following error: undefined: name --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef80117..198bf01 100644 --- a/README.md +++ b/README.md @@ -256,14 +256,15 @@ func main() { // For each matched request Context will hold the route definition router.POST("/user/:name/*action", func(c *gin.Context) { - c.FullPath() == "/user/:name/*action" // true + b := c.FullPath() == "/user/:name/*action" // true + c.String(http.StatusOK, "%t", b) }) // This handler will add a new router for /user/groups. // Exact routes are resolved before param routes, regardless of the order they were defined. // Routes starting with /user/groups are never interpreted as /user/:name/... routes router.GET("/user/groups", func(c *gin.Context) { - c.String(http.StatusOK, "The available groups are [...]", name) + c.String(http.StatusOK, "The available groups are [...]") }) router.Run(":8080") From 435a76b735db28b6223b323d30503de51c637b89 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 19 Aug 2021 09:46:31 +0200 Subject: [PATCH 577/912] chore(ci): update dependencies (#2827) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Matthieu MOREL --- .github/dependabot.yml | 10 ++ .github/workflows/gin.yml | 41 +++-- .golangci.yml | 23 +++ binding/binding_test.go | 3 +- binding/form_mapping.go | 5 +- binding/form_mapping_test.go | 2 +- binding/protobuf.go | 2 +- binding/uri.go | 2 +- context_test.go | 24 ++- go.mod | 14 +- go.sum | 90 ++++++---- mode.go | 6 +- render/json.go | 4 +- render/protobuf.go | 2 +- render/render_test.go | 11 +- response_writer_test.go | 14 +- testdata/protoexample/test.pb.go | 299 +++++++++++++++++++++++++------ tree_test.go | 2 +- 18 files changed, 411 insertions(+), 143 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .golangci.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..632e8eb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 85cc560..ab450ab 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -9,12 +9,31 @@ on: - master jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Setup go + uses: actions/setup-go@v2 + with: + go-version: '^1.16' + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.41.1 + args: --verbose test: strategy: matrix: os: [ubuntu-latest, macos-latest] go: [1.13, 1.14, 1.15, 1.16] test-tags: ['', nomsgpack] + include: + - os: ubuntu-latest + go-build: ~/.cache/go-build + - os: macos-latest + go-build: ~/Library/Caches/go-build name: ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }} runs-on: ${{ matrix.os }} env: @@ -31,21 +50,23 @@ jobs: uses: actions/checkout@v2 with: ref: ${{ github.ref }} - - - name: Install Dependencies - run: make tools - - - name: Run Check - run: | - make vet - make fmt-check - make misspell-check + + - uses: actions/cache@v2 + with: + path: | + ${{ matrix.go-build }} + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- - name: Run Tests run: make test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 + with: + flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} notification-gitter: needs: test runs-on: ubuntu-latest diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..1c3cb75 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,23 @@ +linters: + enable: + - gofmt + - misspell + - revive +issues: + exclude-rules: + - linters: + - deadcode + text: "`static` is unused" + - linters: + - structcheck + - unused + text: "`data` is unused" + - linters: + - staticcheck + text: "SA1019:" + - linters: + - revive + text: "var-naming:" + - linters: + - revive + text: "exported:" diff --git a/binding/binding_test.go b/binding/binding_test.go index 1733617..17df7dc 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -20,8 +20,8 @@ import ( "time" "github.com/gin-gonic/gin/testdata/protoexample" - "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" ) type appkey struct { @@ -832,7 +832,6 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba assert.Equal(t, 1, obj.Page) assert.Equal(t, 2, obj.Size) assert.Equal(t, "test-appkey", obj.Appkey) - } func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { diff --git a/binding/form_mapping.go b/binding/form_mapping.go index cb66dd4..54b3eab 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -26,7 +26,7 @@ var ( ErrConvertToMapString = errors.New("can not convert to map of strings") ) -func mapUri(ptr interface{}, m map[string][]string) error { +func mapURI(ptr interface{}, m map[string][]string) error { return mapFormByTag(ptr, m, "uri") } @@ -83,7 +83,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag return false, nil } - var vKind = value.Kind() + vKind := value.Kind() if vKind == reflect.Ptr { var isNew bool @@ -310,7 +310,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val t := time.Unix(tv/int64(d), tv%int64(d)) value.Set(reflect.ValueOf(t)) return nil - } if val == "" { diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 608594e..516554e 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -131,7 +131,7 @@ func TestMappingURI(t *testing.T) { var s struct { F int `uri:"field"` } - err := mapUri(&s, map[string][]string{"field": {"6"}}) + err := mapURI(&s, map[string][]string{"field": {"6"}}) assert.NoError(t, err) assert.Equal(t, int(6), s.F) } diff --git a/binding/protobuf.go b/binding/protobuf.go index f9ece92..ca02897 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -8,7 +8,7 @@ import ( "io/ioutil" "net/http" - "github.com/golang/protobuf/proto" + "google.golang.org/protobuf/proto" ) type protobufBinding struct{} diff --git a/binding/uri.go b/binding/uri.go index f91ec38..a3c0df5 100644 --- a/binding/uri.go +++ b/binding/uri.go @@ -11,7 +11,7 @@ func (uriBinding) Name() string { } func (uriBinding) BindUri(m map[string][]string, obj interface{}) error { - if err := mapUri(obj, m); err != nil { + if err := mapURI(obj, m); err != nil { return err } return validate(obj) diff --git a/context_test.go b/context_test.go index ffbe5cc..176eaae 100644 --- a/context_test.go +++ b/context_test.go @@ -23,8 +23,8 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" - "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" testdata "github.com/gin-gonic/gin/testdata/protoexample" ) @@ -234,7 +234,6 @@ func TestContextSetGetValues(t *testing.T) { assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2)) assert.Exactly(t, c.MustGet("float64").(float64), 4.2) assert.Exactly(t, c.MustGet("intInterface").(int), 1) - } func TestContextGetString(t *testing.T) { @@ -300,7 +299,7 @@ func TestContextGetStringSlice(t *testing.T) { func TestContextGetStringMap(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - var m = make(map[string]interface{}) + m := make(map[string]interface{}) m["foo"] = 1 c.Set("map", m) @@ -310,7 +309,7 @@ func TestContextGetStringMap(t *testing.T) { func TestContextGetStringMapString(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - var m = make(map[string]string) + m := make(map[string]string) m["foo"] = "bar" c.Set("map", m) @@ -320,7 +319,7 @@ func TestContextGetStringMapString(t *testing.T) { func TestContextGetStringMapStringSlice(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - var m = make(map[string][]string) + m := make(map[string][]string) m["foo"] = []string{"foo"} c.Set("map", m) @@ -369,15 +368,12 @@ func TestContextHandlerNames(t *testing.T) { } func handlerNameTest(c *Context) { - } func handlerNameTest2(c *Context) { - } var handlerTest HandlerFunc = func(c *Context) { - } func TestContextHandler(t *testing.T) { @@ -659,8 +655,7 @@ func TestContextBodyAllowedForStatus(t *testing.T) { assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) } -type TestPanicRender struct { -} +type TestPanicRender struct{} func (*TestPanicRender) Render(http.ResponseWriter) error { return errors.New("TestPanicRender") @@ -1329,7 +1324,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) { _, err := buf.ReadFrom(w.Body) assert.NoError(t, err) jsonStringBody := buf.String() - assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}"), jsonStringBody) + assert.Equal(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) } func TestContextError(t *testing.T) { @@ -1545,6 +1540,7 @@ func TestContextBindWithJSON(t *testing.T) { assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) } + func TestContextBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -2100,6 +2096,8 @@ func TestContextWithFallbackErrFromRequestContext(t *testing.T) { assert.EqualError(t, c2.Err(), context.Canceled.Error()) } +type contextKey string + func TestContextWithFallbackValueFromRequestContext(t *testing.T) { tests := []struct { name string @@ -2122,8 +2120,8 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { getContextAndKey: func() (*Context, interface{}) { c := &Context{} c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request = c.Request.WithContext(context.WithValue(context.TODO(), "key", "value")) - return c, "key" + c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value")) + return c, contextKey("key") }, value: "value", }, diff --git a/go.mod b/go.mod index 9484b26..bf68259 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,12 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.6.1 - github.com/goccy/go-json v0.5.1 - github.com/golang/protobuf v1.3.3 - github.com/json-iterator/go v1.1.9 - github.com/mattn/go-isatty v0.0.12 - github.com/stretchr/testify v1.4.0 + github.com/go-playground/validator/v10 v10.9.0 + github.com/goccy/go-json v0.7.6 + github.com/json-iterator/go v1.1.11 + github.com/mattn/go-isatty v0.0.13 + github.com/stretchr/testify v1.7.0 github.com/ugorji/go/codec v1.2.6 - gopkg.in/yaml.v2 v2.2.8 + google.golang.org/protobuf v1.27.1 + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index e61ef90..351ee25 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -5,51 +6,76 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I= -github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= -github.com/goccy/go-json v0.5.1 h1:R9UYTOUvo7eIY9aeDMZ4L6OVtHaSr1k2No9W6MKjXrA= -github.com/goccy/go-json v0.5.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= +github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/goccy/go-json v0.7.6 h1:H0wq4jppBQ+9222sk5+hPLL25abZQiRuQ6YPnjO9c+A= +github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/mode.go b/mode.go index c8813af..4d199df 100644 --- a/mode.go +++ b/mode.go @@ -41,8 +41,10 @@ var DefaultWriter io.Writer = os.Stdout // DefaultErrorWriter is the default io.Writer used by Gin to debug errors var DefaultErrorWriter io.Writer = os.Stderr -var ginMode = debugCode -var modeName = DebugMode +var ( + ginMode = debugCode + modeName = DebugMode +) func init() { mode := os.Getenv(EnvGinMode) diff --git a/render/json.go b/render/json.go index e25415b..b80adcf 100644 --- a/render/json.go +++ b/render/json.go @@ -49,7 +49,7 @@ type PureJSON struct { var ( jsonContentType = []string{"application/json; charset=utf-8"} jsonpContentType = []string{"application/javascript; charset=utf-8"} - jsonAsciiContentType = []string{"application/json"} + jsonASCIIContentType = []string{"application/json"} ) // Render (JSON) writes data with custom ContentType. @@ -178,7 +178,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { // WriteContentType (AsciiJSON) writes JSON ContentType. func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { - writeContentType(w, jsonAsciiContentType) + writeContentType(w, jsonASCIIContentType) } // Render (PureJSON) writes custom ContentType and encodes the given interface object. diff --git a/render/protobuf.go b/render/protobuf.go index 15aca99..1d2aa87 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -7,7 +7,7 @@ package render import ( "net/http" - "github.com/golang/protobuf/proto" + "google.golang.org/protobuf/proto" ) // ProtoBuf contains the given interface object. diff --git a/render/render_test.go b/render/render_test.go index 353c82b..22e7d5a 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -14,8 +14,8 @@ import ( "strings" "testing" - "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" testdata "github.com/gin-gonic/gin/testdata/protoexample" ) @@ -420,7 +420,8 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { func TestRenderHTMLDebugFiles(t *testing.T) { w := httptest.NewRecorder() - htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"}, + htmlRender := HTMLDebug{ + Files: []string{"../testdata/template/hello.tmpl"}, Glob: "", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, @@ -438,7 +439,8 @@ func TestRenderHTMLDebugFiles(t *testing.T) { func TestRenderHTMLDebugGlob(t *testing.T) { w := httptest.NewRecorder() - htmlRender := HTMLDebug{Files: nil, + htmlRender := HTMLDebug{ + Files: nil, Glob: "../testdata/template/hello*", Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, @@ -455,7 +457,8 @@ func TestRenderHTMLDebugGlob(t *testing.T) { } func TestRenderHTMLDebugPanics(t *testing.T) { - htmlRender := HTMLDebug{Files: nil, + htmlRender := HTMLDebug{ + Files: nil, Glob: "", Delims: Delims{"{{", "}}"}, FuncMap: nil, diff --git a/response_writer_test.go b/response_writer_test.go index 1f113e7..9061d02 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -17,12 +17,14 @@ import ( // func (w *responseWriter) CloseNotify() <-chan bool { // func (w *responseWriter) Flush() { -var _ ResponseWriter = &responseWriter{} -var _ http.ResponseWriter = &responseWriter{} -var _ http.ResponseWriter = ResponseWriter(&responseWriter{}) -var _ http.Hijacker = ResponseWriter(&responseWriter{}) -var _ http.Flusher = ResponseWriter(&responseWriter{}) -var _ http.CloseNotifier = ResponseWriter(&responseWriter{}) +var ( + _ ResponseWriter = &responseWriter{} + _ http.ResponseWriter = &responseWriter{} + _ http.ResponseWriter = ResponseWriter(&responseWriter{}) + _ http.Hijacker = ResponseWriter(&responseWriter{}) + _ http.Flusher = ResponseWriter(&responseWriter{}) + _ http.CloseNotifier = ResponseWriter(&responseWriter{}) +) func init() { SetMode(TestMode) diff --git a/testdata/protoexample/test.pb.go b/testdata/protoexample/test.pb.go index 21997ca..bf45e02 100644 --- a/testdata/protoexample/test.pb.go +++ b/testdata/protoexample/test.pb.go @@ -1,24 +1,24 @@ -// Code generated by protoc-gen-go. +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.0 +// protoc v3.15.8 // source: test.proto -// DO NOT EDIT! -/* -Package protoexample is a generated protocol buffer package. - -It is generated from these files: - test.proto - -It has these top-level messages: - Test -*/ package protoexample -import proto "github.com/golang/protobuf/proto" -import math "math" +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = math.Inf +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) type FOO int32 @@ -26,88 +26,273 @@ const ( FOO_X FOO = 17 ) -var FOO_name = map[int32]string{ - 17: "X", -} -var FOO_value = map[string]int32{ - "X": 17, -} +// Enum value maps for FOO. +var ( + FOO_name = map[int32]string{ + 17: "X", + } + FOO_value = map[string]int32{ + "X": 17, + } +) func (x FOO) Enum() *FOO { p := new(FOO) *p = x return p } + func (x FOO) String() string { - return proto.EnumName(FOO_name, int32(x)) + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FOO) Descriptor() protoreflect.EnumDescriptor { + return file_test_proto_enumTypes[0].Descriptor() +} + +func (FOO) Type() protoreflect.EnumType { + return &file_test_proto_enumTypes[0] +} + +func (x FOO) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) } -func (x *FOO) UnmarshalJSON(data []byte) error { - value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO") + +// Deprecated: Do not use. +func (x *FOO) UnmarshalJSON(b []byte) error { + num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b) if err != nil { return err } - *x = FOO(value) + *x = FOO(num) return nil } +// Deprecated: Use FOO.Descriptor instead. +func (FOO) EnumDescriptor() ([]byte, []int) { + return file_test_proto_rawDescGZIP(), []int{0} +} + type Test struct { - Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` - Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` - Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` - Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` - XXX_unrecognized []byte `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` + Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` + Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` + Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup,json=optionalgroup" json:"optionalgroup,omitempty"` +} + +// Default values for Test fields. +const ( + Default_Test_Type = int32(77) +) + +func (x *Test) Reset() { + *x = Test{} + if protoimpl.UnsafeEnabled { + mi := &file_test_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Test) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *Test) Reset() { *m = Test{} } -func (m *Test) String() string { return proto.CompactTextString(m) } -func (*Test) ProtoMessage() {} +func (*Test) ProtoMessage() {} -const Default_Test_Type int32 = 77 +func (x *Test) ProtoReflect() protoreflect.Message { + mi := &file_test_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} -func (m *Test) GetLabel() string { - if m != nil && m.Label != nil { - return *m.Label +// Deprecated: Use Test.ProtoReflect.Descriptor instead. +func (*Test) Descriptor() ([]byte, []int) { + return file_test_proto_rawDescGZIP(), []int{0} +} + +func (x *Test) GetLabel() string { + if x != nil && x.Label != nil { + return *x.Label } return "" } -func (m *Test) GetType() int32 { - if m != nil && m.Type != nil { - return *m.Type +func (x *Test) GetType() int32 { + if x != nil && x.Type != nil { + return *x.Type } return Default_Test_Type } -func (m *Test) GetReps() []int64 { - if m != nil { - return m.Reps +func (x *Test) GetReps() []int64 { + if x != nil { + return x.Reps } return nil } -func (m *Test) GetOptionalgroup() *Test_OptionalGroup { - if m != nil { - return m.Optionalgroup +func (x *Test) GetOptionalgroup() *Test_OptionalGroup { + if x != nil { + return x.Optionalgroup } return nil } type Test_OptionalGroup struct { - RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` - XXX_unrecognized []byte `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RequiredField *string `protobuf:"bytes,5,req,name=RequiredField" json:"RequiredField,omitempty"` +} + +func (x *Test_OptionalGroup) Reset() { + *x = Test_OptionalGroup{} + if protoimpl.UnsafeEnabled { + mi := &file_test_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Test_OptionalGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Test_OptionalGroup) ProtoMessage() {} + +func (x *Test_OptionalGroup) ProtoReflect() protoreflect.Message { + mi := &file_test_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} } -func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) } -func (*Test_OptionalGroup) ProtoMessage() {} +// Deprecated: Use Test_OptionalGroup.ProtoReflect.Descriptor instead. +func (*Test_OptionalGroup) Descriptor() ([]byte, []int) { + return file_test_proto_rawDescGZIP(), []int{0, 0} +} -func (m *Test_OptionalGroup) GetRequiredField() string { - if m != nil && m.RequiredField != nil { - return *m.RequiredField +func (x *Test_OptionalGroup) GetRequiredField() string { + if x != nil && x.RequiredField != nil { + return *x.RequiredField } return "" } -func init() { - proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value) +var File_test_proto protoreflect.FileDescriptor + +var file_test_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x22, 0xc7, 0x01, 0x0a, 0x04, 0x54, + 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x02, + 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x3a, 0x02, 0x37, 0x37, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, + 0x04, 0x72, 0x65, 0x70, 0x73, 0x12, 0x46, 0x0a, 0x0d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, + 0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0a, 0x32, 0x20, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0d, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x35, 0x0a, + 0x0d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x24, + 0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18, + 0x05, 0x20, 0x02, 0x28, 0x09, 0x52, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x2a, 0x0c, 0x0a, 0x03, 0x46, 0x4f, 0x4f, 0x12, 0x05, 0x0a, 0x01, 0x58, + 0x10, 0x11, +} + +var ( + file_test_proto_rawDescOnce sync.Once + file_test_proto_rawDescData = file_test_proto_rawDesc +) + +func file_test_proto_rawDescGZIP() []byte { + file_test_proto_rawDescOnce.Do(func() { + file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData) + }) + return file_test_proto_rawDescData +} + +var file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_test_proto_goTypes = []interface{}{ + (FOO)(0), // 0: protoexample.FOO + (*Test)(nil), // 1: protoexample.Test + (*Test_OptionalGroup)(nil), // 2: protoexample.Test.OptionalGroup +} +var file_test_proto_depIdxs = []int32{ + 2, // 0: protoexample.Test.optionalgroup:type_name -> protoexample.Test.OptionalGroup + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_test_proto_init() } +func file_test_proto_init() { + if File_test_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Test); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Test_OptionalGroup); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_test_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_test_proto_goTypes, + DependencyIndexes: file_test_proto_depIdxs, + EnumInfos: file_test_proto_enumTypes, + MessageInfos: file_test_proto_msgTypes, + }.Build() + File_test_proto = out.File + file_test_proto_rawDesc = nil + file_test_proto_goTypes = nil + file_test_proto_depIdxs = nil } diff --git a/tree_test.go b/tree_test.go index ea13c30..cbb3734 100644 --- a/tree_test.go +++ b/tree_test.go @@ -843,7 +843,7 @@ func TestTreeInvalidParamsType(t *testing.T) { tree.children[0].nType = 2 // set invalid Params type - params := make(Params, 0, 0) + params := make(Params, 0) // try to trigger slice bounds out of range with capacity 0 tree.getValue("/test", ¶ms, false) From a46dee3a9af4edd9de1694e3ba3592b20c6a8968 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Thu, 19 Aug 2021 10:16:18 +0200 Subject: [PATCH 578/912] Update .golangci.yml (#2829) --- .golangci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 1c3cb75..cfcff00 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,5 @@ +run: + timeout: 5m linters: enable: - gofmt From f3a6b69fd00d7dba9e7cdb726d3c0fc513259834 Mon Sep 17 00:00:00 2001 From: Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> Date: Fri, 20 Aug 2021 03:38:24 +0300 Subject: [PATCH 579/912] Delete unused static const (#2830) --- tree.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tree.go b/tree.go index eb54959..fb0a593 100644 --- a/tree.go +++ b/tree.go @@ -101,8 +101,7 @@ func countParams(path string) uint16 { type nodeType uint8 const ( - static nodeType = iota // default - root + root nodeType = iota + 1 param catchAll ) From 527d950252c03e6c79233cbd7f24f07f81ef3cc6 Mon Sep 17 00:00:00 2001 From: Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> Date: Sat, 21 Aug 2021 09:59:17 +0300 Subject: [PATCH 580/912] Delete unused arg (#2834) --- binding/form_mapping.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 54b3eab..f8b4b12 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -210,7 +210,7 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel case reflect.Int64: switch value.Interface().(type) { case time.Duration: - return setTimeDuration(val, value, field) + return setTimeDuration(val, value) } return setIntField(val, 64, value) case reflect.Uint: @@ -359,7 +359,7 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err return nil } -func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error { +func setTimeDuration(val string, value reflect.Value) error { d, err := time.ParseDuration(val) if err != nil { return err From c30302056176572dc924b89bcb7077de8ffc5000 Mon Sep 17 00:00:00 2001 From: Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> Date: Sat, 21 Aug 2021 16:45:30 +0300 Subject: [PATCH 581/912] bump golangci-lint (#2839) --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index ab450ab..21895f2 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -21,7 +21,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.41.1 + version: v1.42.0 args: --verbose test: strategy: From 820090381719dc209584c3fc1044cd95ec520594 Mon Sep 17 00:00:00 2001 From: Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> Date: Sat, 21 Aug 2021 16:46:52 +0300 Subject: [PATCH 582/912] Delete deadcode exclude rules (#2838) --- .golangci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index cfcff00..78a4259 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,9 +7,6 @@ linters: - revive issues: exclude-rules: - - linters: - - deadcode - text: "`static` is unused" - linters: - structcheck - unused From dfc25f91e0a09099fc6c9f9166bab9044af16143 Mon Sep 17 00:00:00 2001 From: Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> Date: Sun, 22 Aug 2021 04:29:51 +0300 Subject: [PATCH 583/912] Add short func with named return (#2837) --- context.go | 52 ++++++++++++++++++++++++---------------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/context.go b/context.go index 1a95300..56c20f2 100644 --- a/context.go +++ b/context.go @@ -391,9 +391,9 @@ func (c *Context) Param(key string) string { // c.Query("name") == "Manu" // c.Query("value") == "" // c.Query("wtf") == "" -func (c *Context) Query(key string) string { - value, _ := c.GetQuery(key) - return value +func (c *Context) Query(key string) (value string) { + value, _ = c.GetQuery(key) + return } // DefaultQuery returns the keyed url query value if it exists, @@ -427,9 +427,9 @@ func (c *Context) GetQuery(key string) (string, bool) { // QueryArray returns a slice of strings for a given query key. // The length of the slice depends on the number of params with the given key. -func (c *Context) QueryArray(key string) []string { - values, _ := c.GetQueryArray(key) - return values +func (c *Context) QueryArray(key string) (values []string) { + values, _ = c.GetQueryArray(key) + return } func (c *Context) initQueryCache() { @@ -444,18 +444,16 @@ func (c *Context) initQueryCache() { // GetQueryArray returns a slice of strings for a given query key, plus // a boolean value whether at least one value exists for the given key. -func (c *Context) GetQueryArray(key string) ([]string, bool) { +func (c *Context) GetQueryArray(key string) (values []string, ok bool) { c.initQueryCache() - if values, ok := c.queryCache[key]; ok && len(values) > 0 { - return values, true - } - return []string{}, false + values, ok = c.queryCache[key] + return } // QueryMap returns a map for a given query key. -func (c *Context) QueryMap(key string) map[string]string { - dicts, _ := c.GetQueryMap(key) - return dicts +func (c *Context) QueryMap(key string) (dicts map[string]string) { + dicts, _ = c.GetQueryMap(key) + return } // GetQueryMap returns a map for a given query key, plus a boolean value @@ -467,9 +465,9 @@ func (c *Context) GetQueryMap(key string) (map[string]string, bool) { // PostForm returns the specified key from a POST urlencoded form or multipart form // when it exists, otherwise it returns an empty string `("")`. -func (c *Context) PostForm(key string) string { - value, _ := c.GetPostForm(key) - return value +func (c *Context) PostForm(key string) (value string) { + value, _ = c.GetPostForm(key) + return } // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form @@ -498,9 +496,9 @@ func (c *Context) GetPostForm(key string) (string, bool) { // PostFormArray returns a slice of strings for a given form key. // The length of the slice depends on the number of params with the given key. -func (c *Context) PostFormArray(key string) []string { - values, _ := c.GetPostFormArray(key) - return values +func (c *Context) PostFormArray(key string) (values []string) { + values, _ = c.GetPostFormArray(key) + return } func (c *Context) initFormCache() { @@ -518,18 +516,16 @@ func (c *Context) initFormCache() { // GetPostFormArray returns a slice of strings for a given form key, plus // a boolean value whether at least one value exists for the given key. -func (c *Context) GetPostFormArray(key string) ([]string, bool) { +func (c *Context) GetPostFormArray(key string) (values []string, ok bool) { c.initFormCache() - if values := c.formCache[key]; len(values) > 0 { - return values, true - } - return []string{}, false + values, ok = c.formCache[key] + return } // PostFormMap returns a map for a given form key. -func (c *Context) PostFormMap(key string) map[string]string { - dicts, _ := c.GetPostFormMap(key) - return dicts +func (c *Context) PostFormMap(key string) (dicts map[string]string) { + dicts, _ = c.GetPostFormMap(key) + return } // GetPostFormMap returns a map for a given form key, plus a boolean value From 4e7584175d7f2b4245249e769110fd1df0d779db Mon Sep 17 00:00:00 2001 From: Lanco <35420416+lancoLiu@users.noreply.github.com> Date: Mon, 23 Aug 2021 01:32:41 +0800 Subject: [PATCH 584/912] minor tweaks,optimize code (#2788) --- render/json.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/render/json.go b/render/json.go index b80adcf..3ebcee9 100644 --- a/render/json.go +++ b/render/json.go @@ -102,8 +102,7 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { // if the jsonBytes is array values if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes, bytesconv.StringToBytes("]")) { - _, err = w.Write(bytesconv.StringToBytes(r.Prefix)) - if err != nil { + if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil { return err } } @@ -130,20 +129,19 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { } callback := template.JSEscapeString(r.Callback) - _, err = w.Write(bytesconv.StringToBytes(callback)) - if err != nil { + if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil { return err } - _, err = w.Write(bytesconv.StringToBytes("(")) - if err != nil { + + if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil { return err } - _, err = w.Write(ret) - if err != nil { + + if _, err = w.Write(ret); err != nil { return err } - _, err = w.Write(bytesconv.StringToBytes(");")) - if err != nil { + + if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil { return err } From 30cdbfcf4c904bb2bcc0f474ff1660a1efb852da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 09:58:54 +0800 Subject: [PATCH 585/912] Bump github.com/goccy/go-json from 0.7.6 to 0.7.7 (#2849) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.7.6 to 0.7.7. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.7.6...v0.7.7) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bf68259..844dc45 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.9.0 - github.com/goccy/go-json v0.7.6 + github.com/goccy/go-json v0.7.7 github.com/json-iterator/go v1.1.11 github.com/mattn/go-isatty v0.0.13 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 351ee25..266f4ac 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.7.6 h1:H0wq4jppBQ+9222sk5+hPLL25abZQiRuQ6YPnjO9c+A= -github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.7.7 h1:MflzqwHECILPg/0qDYB+jx+sJeNojJHEbRYsa8q7j/o= +github.com/goccy/go-json v0.7.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From e4c026e2a101de574c480d390da41b91efb9490e Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 6 Sep 2021 08:10:06 +0800 Subject: [PATCH 586/912] Fix go1.17 test error (#2856) --- context_1.16_test.go | 31 +++++++++++++++++++++++++++++++ context_1.17_test.go | 33 +++++++++++++++++++++++++++++++++ context_test.go | 13 ------------- 3 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 context_1.16_test.go create mode 100644 context_1.17_test.go diff --git a/context_1.16_test.go b/context_1.16_test.go new file mode 100644 index 0000000..053e6c5 --- /dev/null +++ b/context_1.16_test.go @@ -0,0 +1,31 @@ +// Copyright 2021 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build !go1.17 +// +build !go1.17 + +package gin + +import ( + "bytes" + "mime/multipart" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContextFormFileFailed16(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + c.engine.MaxMultipartMemory = 8 << 20 + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) +} diff --git a/context_1.17_test.go b/context_1.17_test.go new file mode 100644 index 0000000..431d54c --- /dev/null +++ b/context_1.17_test.go @@ -0,0 +1,33 @@ +// Copyright 2021 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go1.17 +// +build go1.17 + +package gin + +import ( + "bytes" + "mime/multipart" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContextFormFileFailed17(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + c.engine.MaxMultipartMemory = 8 << 20 + assert.Panics(t, func() { + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) + }) +} diff --git a/context_test.go b/context_test.go index 176eaae..7c5626e 100644 --- a/context_test.go +++ b/context_test.go @@ -87,19 +87,6 @@ func TestContextFormFile(t *testing.T) { assert.NoError(t, c.SaveUploadedFile(f, "test")) } -func TestContextFormFileFailed(t *testing.T) { - buf := new(bytes.Buffer) - mw := multipart.NewWriter(buf) - mw.Close() - c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Set("Content-Type", mw.FormDataContentType()) - c.engine.MaxMultipartMemory = 8 << 20 - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) -} - func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) From a550c568d7416df89c6b29615a6f977880c668f2 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 6 Sep 2021 08:21:03 +0800 Subject: [PATCH 587/912] chore: Add go1.17 for testing (#2828) Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 21895f2..0ebe2ec 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -27,7 +27,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.13, 1.14, 1.15, 1.16] + go: [1.13, 1.14, 1.15, 1.16, 1.17] test-tags: ['', nomsgpack] include: - os: ubuntu-latest From abcf32f5add9f52704b9aad6e554411b0716e242 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Sep 2021 06:43:08 +0800 Subject: [PATCH 588/912] Bump github.com/goccy/go-json from 0.7.7 to 0.7.8 (#2859) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.7.7 to 0.7.8. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.7.7...v0.7.8) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 844dc45..26b9271 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.9.0 - github.com/goccy/go-json v0.7.7 + github.com/goccy/go-json v0.7.8 github.com/json-iterator/go v1.1.11 github.com/mattn/go-isatty v0.0.13 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 266f4ac..8aaf41a 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.7.7 h1:MflzqwHECILPg/0qDYB+jx+sJeNojJHEbRYsa8q7j/o= -github.com/goccy/go-json v0.7.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.7.8 h1:CvMH7LotYymYuLGEohBM1lTZWX4g6jzWUUl2aLFuBoE= +github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From 5c62979390881dcaf31e4565d6cc4e5d3e211afc Mon Sep 17 00:00:00 2001 From: Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> Date: Tue, 7 Sep 2021 01:43:42 +0300 Subject: [PATCH 589/912] bump golangci-lint version (#2858) --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 0ebe2ec..6eb5a64 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -21,7 +21,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.42.0 + version: v1.42.1 args: --verbose test: strategy: From deb83b6365cf1b95f50929492f6058c94a6ff274 Mon Sep 17 00:00:00 2001 From: filikos <11477309+filikos@users.noreply.github.com> Date: Tue, 7 Sep 2021 04:08:45 +0200 Subject: [PATCH 590/912] gin.Context.SetParam shortcut for e2e tests (#2848) * Added SetParam shortcut for e2e tests, added SetParam test * Adjusted naming and formatting * fixed typo --- context.go | 9 +++++++++ context_test.go | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/context.go b/context.go index 56c20f2..6284948 100644 --- a/context.go +++ b/context.go @@ -383,6 +383,15 @@ func (c *Context) Param(key string) string { return c.Params.ByName(key) } +// AddParam adds param to context and +// replaces path param key with given value for e2e testing purposes +// Example Route: "/user/:id" +// AddParam("id", 1) +// Result: "/user/1" +func (c *Context) AddParam(key, value string) { + c.Params = append(c.Params, Param{Key: key, Value: value}) +} + // Query returns the keyed url query value if it exists, // otherwise it returns an empty string `("")`. // It is shortcut for `c.Request.URL.Query().Get(key)` diff --git a/context_test.go b/context_test.go index 7c5626e..f9792e3 100644 --- a/context_test.go +++ b/context_test.go @@ -2137,3 +2137,14 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { }) } } + +func TestContextAddParam(t *testing.T) { + c := &Context{} + id := "id" + value := "1" + c.AddParam(id, value) + + v, ok := c.Params.Get(id) + assert.Equal(t, ok, true) + assert.Equal(t, value, v) +} From eab47b5423b67608ffe099ce2f851f3d48ef4a96 Mon Sep 17 00:00:00 2001 From: Tevic Date: Tue, 7 Sep 2021 10:10:32 +0800 Subject: [PATCH 591/912] fix: check obj type in protobufBinding (#2851) * fix: check obj type in protobufBinding * fix: UnitTest for invalid proto obj --- binding/binding_test.go | 7 +++++++ binding/protobuf.go | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 17df7dc..5b0ce39 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1339,6 +1339,13 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body err := b.Bind(req, &obj) assert.Error(t, err) + invalid_obj := FooStruct{} + req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`)) + req.Header.Add("Content-Type", MIMEPROTOBUF) + err = b.Bind(req, &invalid_obj) + assert.Error(t, err) + assert.Equal(t, err.Error(), "obj is not ProtoMessage") + obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) diff --git a/binding/protobuf.go b/binding/protobuf.go index ca02897..a4e4715 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -5,6 +5,7 @@ package binding import ( + "errors" "io/ioutil" "net/http" @@ -26,7 +27,11 @@ func (b protobufBinding) Bind(req *http.Request, obj interface{}) error { } func (protobufBinding) BindBody(body []byte, obj interface{}) error { - if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil { + msg, ok := obj.(proto.Message) + if !ok { + return errors.New("obj is not ProtoMessage") + } + if err := proto.Unmarshal(body, msg); err != nil { return err } // Here it's same to return validate(obj), but util now we can't add From ae349b4015f4736e44ea813365dcf51094329b95 Mon Sep 17 00:00:00 2001 From: wssccc Date: Tue, 7 Sep 2021 13:05:19 +0800 Subject: [PATCH 592/912] Fix typo (#2860) --- tree_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tree_test.go b/tree_test.go index cbb3734..8ae5b7d 100644 --- a/tree_test.go +++ b/tree_test.go @@ -437,7 +437,7 @@ func TestTreeChildConflict(t *testing.T) { testRoutes(t, routes) } -func TestTreeDupliatePath(t *testing.T) { +func TestTreeDuplicatePath(t *testing.T) { tree := &node{} routes := [...]string{ From 3a6f18f32f22d7978bbafdf9b81d3a568b7a5868 Mon Sep 17 00:00:00 2001 From: Henry Yee Date: Wed, 8 Sep 2021 11:30:55 +0800 Subject: [PATCH 593/912] fixed SetOutput() panics on go 1.17 (#2861) * fixed SetOutput() panics on go 1.17 * update go.sum --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 26b9271..92cca49 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/go-playground/validator/v10 v10.9.0 github.com/goccy/go-json v0.7.8 github.com/json-iterator/go v1.1.11 - github.com/mattn/go-isatty v0.0.13 + github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 github.com/ugorji/go/codec v1.2.6 google.golang.org/protobuf v1.27.1 diff --git a/go.sum b/go.sum index 8aaf41a..969d49c 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= @@ -54,9 +54,9 @@ github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxW golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From 71f70870976cf304cdb3942a871c15a20f5d35c5 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Tue, 21 Sep 2021 09:22:21 +0200 Subject: [PATCH 594/912] golangci(lint) : more linters (#2870) --- .github/workflows/gin.yml | 1 + .golangci.yml | 13 +++++++++++++ binding/form.go | 3 ++- context.go | 5 +++-- context_test.go | 3 +-- recovery.go | 4 +++- render/render_test.go | 3 +-- 7 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 6eb5a64..15c2530 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,6 +24,7 @@ jobs: version: v1.42.1 args: --verbose test: + needs: lint strategy: matrix: os: [ubuntu-latest, macos-latest] diff --git a/.golangci.yml b/.golangci.yml index 78a4259..ba5f4d1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,9 +2,22 @@ run: timeout: 5m linters: enable: + - asciicheck + - depguard + - dogsled + - durationcheck + - errcheck + - errorlint + - exportloopref + - gci - gofmt + - goimports - misspell + - nakedret + - nilerr + - nolintlint - revive + - wastedassign issues: exclude-rules: - linters: diff --git a/binding/form.go b/binding/form.go index 040af9e..fa2a654 100644 --- a/binding/form.go +++ b/binding/form.go @@ -5,6 +5,7 @@ package binding import ( + "errors" "net/http" ) @@ -22,7 +23,7 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } - if err := req.ParseMultipartForm(defaultMemory); err != nil && err != http.ErrNotMultipart { + if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) { return err } if err := mapForm(obj, req.Form); err != nil { diff --git a/context.go b/context.go index 6284948..8a2f46d 100644 --- a/context.go +++ b/context.go @@ -220,7 +220,8 @@ func (c *Context) Error(err error) *Error { panic("err is nil") } - parsedError, ok := err.(*Error) + var parsedError *Error + ok := errors.As(err, &parsedError) if !ok { parsedError = &Error{ Err: err, @@ -515,7 +516,7 @@ func (c *Context) initFormCache() { c.formCache = make(url.Values) req := c.Request if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { - if err != http.ErrNotMultipart { + if !errors.Is(err, http.ErrNotMultipart) { debugPrint("error on parse multipart form array: %v", err) } } diff --git a/context_test.go b/context_test.go index f9792e3..ad16fbb 100644 --- a/context_test.go +++ b/context_test.go @@ -23,10 +23,9 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" + testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" - - testdata "github.com/gin-gonic/gin/testdata/protoexample" ) var _ context.Context = &Context{} diff --git a/recovery.go b/recovery.go index 3101fe2..39f1355 100644 --- a/recovery.go +++ b/recovery.go @@ -6,6 +6,7 @@ package gin import ( "bytes" + "errors" "fmt" "io" "io/ioutil" @@ -60,7 +61,8 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { // condition that warrants a panic stack trace. var brokenPipe bool if ne, ok := err.(*net.OpError); ok { - if se, ok := ne.Err.(*os.SyscallError); ok { + var se *os.SyscallError + if errors.As(ne, &se) { if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { brokenPipe = true } diff --git a/render/render_test.go b/render/render_test.go index 22e7d5a..e417731 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -14,10 +14,9 @@ import ( "strings" "testing" + testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" - - testdata "github.com/gin-gonic/gin/testdata/protoexample" ) // TODO unit tests From e052bf31aac21ea25d970bb2f2f93112a904abdb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Sep 2021 15:23:31 +0800 Subject: [PATCH 595/912] Bump github.com/json-iterator/go from 1.1.11 to 1.1.12 (#2865) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 92cca49..80807e8 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.9.0 github.com/goccy/go-json v0.7.8 - github.com/json-iterator/go v1.1.11 + github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 github.com/ugorji/go/codec v1.2.6 diff --git a/go.sum b/go.sum index 969d49c..33ce8cd 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -34,8 +34,8 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= From ef168679ce5e6c15aefb460787e977bacc3c38f3 Mon Sep 17 00:00:00 2001 From: Aravinth Sundaram Date: Tue, 28 Sep 2021 06:27:36 +0530 Subject: [PATCH 596/912] Correcting grammatical errors in README file (#2880) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 198bf01..10be5e1 100644 --- a/README.md +++ b/README.md @@ -1827,7 +1827,7 @@ func main() { quit := make(chan os.Signal) // kill (no param) default send syscall.SIGTERM // kill -2 is syscall.SIGINT - // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it + // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") From d6534ccf3893b74738c78f51f8cb880c34db4ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AF=BB=E5=AF=BB=E8=A7=85=E8=A7=85=E7=9A=84Gopher?= Date: Tue, 28 Sep 2021 09:45:50 +0800 Subject: [PATCH 597/912] turn on HandleMethodNotAllowed when using NoMethod #2871 (#2872) --- gin.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 6ab2be6..ae6d479 100644 --- a/gin.go +++ b/gin.go @@ -264,8 +264,9 @@ func (engine *Engine) NoRoute(handlers ...HandlerFunc) { engine.rebuild404Handlers() } -// NoMethod sets the handlers called when... TODO. +// NoMethod sets the handlers called when NoMethod. func (engine *Engine) NoMethod(handlers ...HandlerFunc) { + engine.HandleMethodNotAllowed = true engine.noMethod = handlers engine.rebuild405Handlers() } From f469c1be3925ab32bd38fbf944b230c70abe9e2b Mon Sep 17 00:00:00 2001 From: Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> Date: Tue, 28 Sep 2021 06:37:31 +0300 Subject: [PATCH 598/912] Add gosec (#2882) --- .golangci.yml | 62 +++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ba5f4d1..c5e1de3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,34 +2,38 @@ run: timeout: 5m linters: enable: - - asciicheck - - depguard - - dogsled - - durationcheck - - errcheck - - errorlint - - exportloopref - - gci - - gofmt - - goimports - - misspell - - nakedret - - nilerr - - nolintlint - - revive - - wastedassign + - asciicheck + - depguard + - dogsled + - durationcheck + - errcheck + - errorlint + - exportloopref + - gci + - gofmt + - goimports + - gosec + - misspell + - nakedret + - nilerr + - nolintlint + - revive + - wastedassign issues: exclude-rules: - - linters: - - structcheck - - unused - text: "`data` is unused" - - linters: - - staticcheck - text: "SA1019:" - - linters: - - revive - text: "var-naming:" - - linters: - - revive - text: "exported:" + - linters: + - structcheck + - unused + text: "`data` is unused" + - linters: + - staticcheck + text: "SA1019:" + - linters: + - revive + text: "var-naming:" + - linters: + - revive + text: "exported:" + - path: _test\.go + linters: + - gosec # security is not make sense in tests From 97b3c0d88ada639763414f35715f88d6bd38fa10 Mon Sep 17 00:00:00 2001 From: joeADSP <75027008+joeADSP@users.noreply.github.com> Date: Wed, 29 Sep 2021 00:35:06 +0100 Subject: [PATCH 599/912] Fix grammatical and spelling errors in context.go (#2883) --- context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 8a2f46d..370f608 100644 --- a/context.go +++ b/context.go @@ -735,8 +735,8 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e // ClientIP implements a best effort algorithm to return the real client IP. // It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. -// If it's it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). -// If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy, +// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). +// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy, // the remote IP (coming form Request.RemoteAddr) is returned. func (c *Context) ClientIP() string { // Check if we're running on a trusted platform From 6d75aba83f7733d505b830bbe224583f1ef3771e Mon Sep 17 00:00:00 2001 From: Notealot <714804968@qq.com> Date: Wed, 29 Sep 2021 19:26:02 +0800 Subject: [PATCH 600/912] Quick Fix c.ClientIP() mistakely parsing to 127.0.0.1 for who not using r.Run() to run http server (#2832) --- gin.go | 4 ++-- middleware_test.go | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index ae6d479..02a1062 100644 --- a/gin.go +++ b/gin.go @@ -183,6 +183,7 @@ func New() *Engine { trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJSONPrefix: "while(1);", + trustedCIDRs: []*net.IPNet{{IP: net.IP{0x0, 0x0, 0x0, 0x0}, Mask: net.IPMask{0x0, 0x0, 0x0, 0x0}}}, } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { @@ -264,9 +265,8 @@ func (engine *Engine) NoRoute(handlers ...HandlerFunc) { engine.rebuild404Handlers() } -// NoMethod sets the handlers called when NoMethod. +// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true. func (engine *Engine) NoMethod(handlers ...HandlerFunc) { - engine.HandleMethodNotAllowed = true engine.noMethod = handlers engine.rebuild405Handlers() } diff --git a/middleware_test.go b/middleware_test.go index fca1c53..4b4afd4 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -118,7 +118,10 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) { func TestMiddlewareNoMethodDisabled(t *testing.T) { signature := "" router := New() + + // NoMethod disabled router.HandleMethodNotAllowed = false + router.Use(func(c *Context) { signature += "A" c.Next() @@ -144,6 +147,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) { router.POST("/", func(c *Context) { signature += " XX " }) + // RUN w := performRequest(router, "GET", "/") From 1a2bc0e7cb1a69b7750bd652d92c4d2b41504f04 Mon Sep 17 00:00:00 2001 From: axiaoxin <254606826@qq.com> Date: Thu, 30 Sep 2021 10:04:28 +0800 Subject: [PATCH 601/912] =?UTF-8?q?setted=20typo=20fix:=20There=E2=80=99s?= =?UTF-8?q?=20no=20such=20word=20as=20`setted`,=20`set`=20is=20set,=20set,?= =?UTF-8?q?=20setting=20(#2886)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- binding/form_mapping.go | 18 +++++++++--------- binding/multipart_form_mapping.go | 16 ++++++++-------- errors_test.go | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index f8b4b12..fa7ad1b 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -61,7 +61,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { // setter tries to set value on a walking by fields of a struct type setter interface { - TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) + TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error) } type formSource map[string][]string @@ -69,7 +69,7 @@ type formSource map[string][]string var _ setter = formSource(nil) // TrySet tries to set a value by request's form source (like map[string][]string) -func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { +func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) { return setByForm(value, field, form, tagValue, opt) } @@ -92,14 +92,14 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag isNew = true vPtr = reflect.New(value.Type().Elem()) } - isSetted, err := mapping(vPtr.Elem(), field, setter, tag) + isSet, err := mapping(vPtr.Elem(), field, setter, tag) if err != nil { return false, err } - if isNew && isSetted { + if isNew && isSet { value.Set(vPtr) } - return isSetted, nil + return isSet, nil } if vKind != reflect.Struct || !field.Anonymous { @@ -115,7 +115,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag if vKind == reflect.Struct { tValue := value.Type() - var isSetted bool + var isSet bool for i := 0; i < value.NumField(); i++ { sf := tValue.Field(i) if sf.PkgPath != "" && !sf.Anonymous { // unexported @@ -125,9 +125,9 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag if err != nil { return false, err } - isSetted = isSetted || ok + isSet = isSet || ok } - return isSetted, nil + return isSet, nil } return false, nil } @@ -164,7 +164,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter return setter.TrySet(value, field, tagValue, setOpt) } -func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) { +func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) { vs, ok := form[tagValue] if !ok && !opt.isDefaultExists { return false, nil diff --git a/binding/multipart_form_mapping.go b/binding/multipart_form_mapping.go index 69c0a54..c4d7ed7 100644 --- a/binding/multipart_form_mapping.go +++ b/binding/multipart_form_mapping.go @@ -32,7 +32,7 @@ func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField return setByForm(value, field, r.MultipartForm.Value, key, opt) } -func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { +func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) { switch value.Kind() { case reflect.Ptr: switch value.Interface().(type) { @@ -48,9 +48,9 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file } case reflect.Slice: slice := reflect.MakeSlice(value.Type(), len(files), len(files)) - isSetted, err = setArrayOfMultipartFormFiles(slice, field, files) - if err != nil || !isSetted { - return isSetted, err + isSet, err = setArrayOfMultipartFormFiles(slice, field, files) + if err != nil || !isSet { + return isSet, err } value.Set(slice) return true, nil @@ -60,14 +60,14 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file return false, ErrMultiFileHeader } -func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { +func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) { if value.Len() != len(files) { return false, ErrMultiFileHeaderLenInvalid } for i := range files { - setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1]) - if err != nil || !setted { - return setted, err + set, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1]) + if err != nil || !set { + return set, err } } return true, nil diff --git a/errors_test.go b/errors_test.go index ee95ab3..9a17d85 100644 --- a/errors_test.go +++ b/errors_test.go @@ -113,7 +113,7 @@ func (e TestErr) Error() string { return string(e) } // TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()". // "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13. func TestErrorUnwrap(t *testing.T) { - innerErr := TestErr("somme error") + innerErr := TestErr("some error") // 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr err := fmt.Errorf("wrapped: %w", &Error{ From 39181329dee3a14fa2210c72245811c07d5e72b6 Mon Sep 17 00:00:00 2001 From: Notealot <714804968@qq.com> Date: Wed, 6 Oct 2021 09:37:25 +0800 Subject: [PATCH 602/912] Tidy: Complete TrustedProxies feature (#2887) --- README.md | 12 +++++-- context.go | 2 +- context_test.go | 4 +++ gin.go | 70 +++++++++++++++++++++++------------------ gin_integration_test.go | 7 +++++ gin_test.go | 57 +++++++++++---------------------- 6 files changed, 80 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 10be5e1..62094ff 100644 --- a/README.md +++ b/README.md @@ -2202,11 +2202,17 @@ Gin lets you specify which headers to hold the real client IP (if any), as well as specifying which proxies (or direct clients) you trust to specify one of these headers. -The `TrustedProxies` slice on your `gin.Engine` specifes network addresses or -network CIDRs from where clients which their request headers related to client +Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses +or network CIDRs from where clients which their request headers related to client IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs. +**Attention:** Gin trust all proxies by default if you don't specify a trusted +proxy using the function above, **this is NOT safe**. At the same time, if you don't +use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`, +then `Context.ClientIP()` will return the remote address directly to avoid some +unnecessary computation. + ```go import ( "fmt" @@ -2217,7 +2223,7 @@ import ( func main() { router := gin.Default() - router.TrustedProxies = []string{"192.168.1.2"} + router.SetTrustedProxies([]string{"192.168.1.2"}) router.GET("/", func(c *gin.Context) { // If the client is 192.168.1.2, use the X-Forwarded-For diff --git a/context.go b/context.go index 370f608..d211098 100644 --- a/context.go +++ b/context.go @@ -778,7 +778,7 @@ func (c *Context) ClientIP() string { // RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port). // It also checks if the remoteIP is a trusted proxy or not. // In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks -// defined in Engine.TrustedProxies +// defined by Engine.SetTrustedProxies() func (c *Context) RemoteIP() (net.IP, bool) { ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)) if err != nil { diff --git a/context_test.go b/context_test.go index ad16fbb..07a35f5 100644 --- a/context_test.go +++ b/context_test.go @@ -1409,6 +1409,10 @@ func TestContextClientIP(t *testing.T) { c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"} assert.Equal(t, "40.40.40.40", c.ClientIP()) + // Disabled TrustedProxies feature + _ = c.engine.SetTrustedProxies(nil) + assert.Equal(t, "40.40.40.40", c.ClientIP()) + // Last proxy is trusted, but the RemoteAddr is not _ = c.engine.SetTrustedProxies([]string{"30.30.30.30"}) assert.Equal(t, "40.40.40.40", c.ClientIP()) diff --git a/gin.go b/gin.go index 02a1062..af83161 100644 --- a/gin.go +++ b/gin.go @@ -11,6 +11,7 @@ import ( "net/http" "os" "path" + "reflect" "strings" "sync" @@ -27,6 +28,8 @@ var ( var defaultPlatform string +var defaultTrustedCIDRs = []*net.IPNet{{IP: net.IP{0x0, 0x0, 0x0, 0x0}, Mask: net.IPMask{0x0, 0x0, 0x0, 0x0}}} // 0.0.0.0/0 + // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -119,15 +122,9 @@ type Engine struct { // List of headers used to obtain the client IP when // `(*gin.Engine).ForwardedByClientIP` is `true` and // `(*gin.Context).Request.RemoteAddr` is matched by at least one of the - // network origins of `(*gin.Engine).TrustedProxies`. + // network origins of list defined by `(*gin.Engine).SetTrustedProxies()`. RemoteIPHeaders []string - // List of network origins (IPv4 addresses, IPv4 CIDRs, IPv6 addresses or - // IPv6 CIDRs) from which to trust request's headers that contain - // alternative client IP when `(*gin.Engine).ForwardedByClientIP` is - // `true`. - TrustedProxies []string - // If set to a constant of value gin.Platform*, trusts the headers set by // that platform, for example to determine the client IP TrustedPlatform string @@ -147,6 +144,7 @@ type Engine struct { pool sync.Pool trees methodTrees maxParams uint16 + trustedProxies []string trustedCIDRs []*net.IPNet } @@ -174,7 +172,6 @@ func New() *Engine { HandleMethodNotAllowed: false, ForwardedByClientIP: true, RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"}, - TrustedProxies: []string{"0.0.0.0/0"}, TrustedPlatform: defaultPlatform, UseRawPath: false, RemoveExtraSlash: false, @@ -183,7 +180,8 @@ func New() *Engine { trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJSONPrefix: "while(1);", - trustedCIDRs: []*net.IPNet{{IP: net.IP{0x0, 0x0, 0x0, 0x0}, Mask: net.IPMask{0x0, 0x0, 0x0, 0x0}}}, + trustedProxies: []string{"0.0.0.0/0"}, + trustedCIDRs: defaultTrustedCIDRs, } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { @@ -342,9 +340,9 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() - err = engine.parseTrustedProxies() - if err != nil { - return err + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") } address := resolveAddress(addr) @@ -354,12 +352,12 @@ func (engine *Engine) Run(addr ...string) (err error) { } func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) { - if engine.TrustedProxies == nil { + if engine.trustedProxies == nil { return nil, nil } - cidr := make([]*net.IPNet, 0, len(engine.TrustedProxies)) - for _, trustedProxy := range engine.TrustedProxies { + cidr := make([]*net.IPNet, 0, len(engine.trustedProxies)) + for _, trustedProxy := range engine.trustedProxies { if !strings.Contains(trustedProxy, "/") { ip := parseIP(trustedProxy) if ip == nil { @@ -382,13 +380,25 @@ func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) { return cidr, nil } -// SetTrustedProxies set Engine.TrustedProxies +// SetTrustedProxies set a list of network origins (IPv4 addresses, +// IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust +// request's headers that contain alternative client IP when +// `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies` +// feature is enabled by default, and it also trusts all proxies +// by default. If you want to disable this feature, use +// Engine.SetTrustedProxies(nil), then Context.ClientIP() will +// return the remote address directly. func (engine *Engine) SetTrustedProxies(trustedProxies []string) error { - engine.TrustedProxies = trustedProxies + engine.trustedProxies = trustedProxies return engine.parseTrustedProxies() } -// parseTrustedProxies parse Engine.TrustedProxies to Engine.trustedCIDRs +// isUnsafeTrustedProxies compares Engine.trustedCIDRs and defaultTrustedCIDRs, it's not safe if equal (returns true) +func (engine *Engine) isUnsafeTrustedProxies() bool { + return reflect.DeepEqual(engine.trustedCIDRs, defaultTrustedCIDRs) +} + +// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs func (engine *Engine) parseTrustedProxies() error { trustedCIDRs, err := engine.prepareTrustedCIDRs() engine.trustedCIDRs = trustedCIDRs @@ -416,9 +426,9 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { debugPrint("Listening and serving HTTPS on %s\n", addr) defer func() { debugPrintError(err) }() - err = engine.parseTrustedProxies() - if err != nil { - return err + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") } err = http.ListenAndServeTLS(addr, certFile, keyFile, engine) @@ -432,9 +442,9 @@ func (engine *Engine) RunUnix(file string) (err error) { debugPrint("Listening and serving HTTP on unix:/%s", file) defer func() { debugPrintError(err) }() - err = engine.parseTrustedProxies() - if err != nil { - return err + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") } listener, err := net.Listen("unix", file) @@ -455,9 +465,9 @@ func (engine *Engine) RunFd(fd int) (err error) { debugPrint("Listening and serving HTTP on fd@%d", fd) defer func() { debugPrintError(err) }() - err = engine.parseTrustedProxies() - if err != nil { - return err + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") } f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) @@ -476,9 +486,9 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) { debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr()) defer func() { debugPrintError(err) }() - err = engine.parseTrustedProxies() - if err != nil { - return err + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") } err = http.Serve(listener, engine) diff --git a/gin_integration_test.go b/gin_integration_test.go index 094c46e..0b67b54 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -76,6 +76,12 @@ func TestRunEmpty(t *testing.T) { testRequest(t, "http://localhost:8080/example") } +func TestBadTrustedCIDRs(t *testing.T) { + router := New() + assert.Error(t, router.SetTrustedProxies([]string{"hello/world"})) +} + +/* legacy tests func TestBadTrustedCIDRsForRun(t *testing.T) { os.Setenv("PORT", "") router := New() @@ -143,6 +149,7 @@ func TestBadTrustedCIDRsForRunTLS(t *testing.T) { router.TrustedProxies = []string{"hello/world"} assert.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) } +*/ func TestRunTLS(t *testing.T) { router := New() diff --git a/gin_test.go b/gin_test.go index 678d95f..21c43d1 100644 --- a/gin_test.go +++ b/gin_test.go @@ -539,19 +539,15 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { // valid ipv4 cidr { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")} - r.TrustedProxies = []string{"0.0.0.0/0"} - - trustedCIDRs, err := r.prepareTrustedCIDRs() + err := r.SetTrustedProxies([]string{"0.0.0.0/0"}) assert.NoError(t, err) - assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } // invalid ipv4 cidr { - r.TrustedProxies = []string{"192.168.1.33/33"} - - _, err := r.prepareTrustedCIDRs() + err := r.SetTrustedProxies([]string{"192.168.1.33/33"}) assert.Error(t, err) } @@ -559,19 +555,16 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { // valid ipv4 address { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("192.168.1.33/32")} - r.TrustedProxies = []string{"192.168.1.33"} - trustedCIDRs, err := r.prepareTrustedCIDRs() + err := r.SetTrustedProxies([]string{"192.168.1.33"}) assert.NoError(t, err) - assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } // invalid ipv4 address { - r.TrustedProxies = []string{"192.168.1.256"} - - _, err := r.prepareTrustedCIDRs() + err := r.SetTrustedProxies([]string{"192.168.1.256"}) assert.Error(t, err) } @@ -579,19 +572,15 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { // valid ipv6 address { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")} - r.TrustedProxies = []string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"} - - trustedCIDRs, err := r.prepareTrustedCIDRs() + err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"}) assert.NoError(t, err) - assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } // invalid ipv6 address { - r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"} - - _, err := r.prepareTrustedCIDRs() + err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"}) assert.Error(t, err) } @@ -599,19 +588,15 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { // valid ipv6 cidr { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")} - r.TrustedProxies = []string{"::/0"} - - trustedCIDRs, err := r.prepareTrustedCIDRs() + err := r.SetTrustedProxies([]string{"::/0"}) assert.NoError(t, err) - assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } // invalid ipv6 cidr { - r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"} - - _, err := r.prepareTrustedCIDRs() + err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"}) assert.Error(t, err) } @@ -623,36 +608,32 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { parseCIDR("192.168.0.0/16"), parseCIDR("172.16.0.1/32"), } - r.TrustedProxies = []string{ + err := r.SetTrustedProxies([]string{ "::/0", "192.168.0.0/16", "172.16.0.1", - } - - trustedCIDRs, err := r.prepareTrustedCIDRs() + }) assert.NoError(t, err) - assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs) + assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } // invalid combination { - r.TrustedProxies = []string{ + err := r.SetTrustedProxies([]string{ "::/0", "192.168.0.0/16", "172.16.0.256", - } - _, err := r.prepareTrustedCIDRs() + }) assert.Error(t, err) } // nil value { - r.TrustedProxies = nil - trustedCIDRs, err := r.prepareTrustedCIDRs() + err := r.SetTrustedProxies(nil) - assert.Nil(t, trustedCIDRs) + assert.Nil(t, r.trustedCIDRs) assert.Nil(t, err) } From 21125bbb3f550dbfa4c64151f7e01f58dd64e2b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Oct 2021 09:38:42 +0800 Subject: [PATCH 603/912] Bump github.com/goccy/go-json from 0.7.8 to 0.7.9 (#2891) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 80807e8..c03439a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.9.0 - github.com/goccy/go-json v0.7.8 + github.com/goccy/go-json v0.7.9 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 33ce8cd..7bf71f9 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.7.8 h1:CvMH7LotYymYuLGEohBM1lTZWX4g6jzWUUl2aLFuBoE= -github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.7.9 h1:mSp3uo1tr6MXQTYopSNhHTUnJhd2zQ4Yk+HdJZP+ZRY= +github.com/goccy/go-json v0.7.9/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From 5929d521715610c9dd14898ebbe1d188d5de8937 Mon Sep 17 00:00:00 2001 From: Egor Seredin <4819888+agmt@users.noreply.github.com> Date: Sat, 9 Oct 2021 09:38:51 +0900 Subject: [PATCH 604/912] ClientIP: check every proxy for trustiness (#2844) --- context.go | 39 ++++++++++++++++++++------------------- context_test.go | 2 +- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/context.go b/context.go index d211098..bea95cc 100644 --- a/context.go +++ b/context.go @@ -766,7 +766,7 @@ func (c *Context) ClientIP() string { if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil { for _, headerName := range c.engine.RemoteIPHeaders { - ip, valid := validateHeader(c.requestHeader(headerName)) + ip, valid := c.engine.validateHeader(c.requestHeader(headerName)) if valid { return ip } @@ -775,6 +775,17 @@ func (c *Context) ClientIP() string { return remoteIP.String() } +func (e *Engine) isTrustedProxy(ip net.IP) bool { + if e.trustedCIDRs != nil { + for _, cidr := range e.trustedCIDRs { + if cidr.Contains(ip) { + return true + } + } + } + return false +} + // RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port). // It also checks if the remoteIP is a trusted proxy or not. // In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks @@ -789,35 +800,25 @@ func (c *Context) RemoteIP() (net.IP, bool) { return nil, false } - if c.engine.trustedCIDRs != nil { - for _, cidr := range c.engine.trustedCIDRs { - if cidr.Contains(remoteIP) { - return remoteIP, true - } - } - } - - return remoteIP, false + return remoteIP, c.engine.isTrustedProxy(remoteIP) } -func validateHeader(header string) (clientIP string, valid bool) { +func (e *Engine) validateHeader(header string) (clientIP string, valid bool) { if header == "" { return "", false } items := strings.Split(header, ",") - for i, ipStr := range items { - ipStr = strings.TrimSpace(ipStr) + for i := len(items) - 1; i >= 0; i-- { + ipStr := strings.TrimSpace(items[i]) ip := net.ParseIP(ipStr) if ip == nil { return "", false } - // We need to return the first IP in the list, but, - // we should not early return since we need to validate that - // the rest of the header is syntactically valid - if i == 0 { - clientIP = ipStr - valid = true + // X-Forwarded-For is appended by proxy + // Check IPs in reverse order and stop when find untrusted proxy + if (i == 0) || (!e.isTrustedProxy(ip)) { + return ipStr, true } } return diff --git a/context_test.go b/context_test.go index 07a35f5..e9fe88f 100644 --- a/context_test.go +++ b/context_test.go @@ -1419,7 +1419,7 @@ func TestContextClientIP(t *testing.T) { // Only trust RemoteAddr _ = c.engine.SetTrustedProxies([]string{"40.40.40.40"}) - assert.Equal(t, "20.20.20.20", c.ClientIP()) + assert.Equal(t, "30.30.30.30", c.ClientIP()) // All steps are trusted _ = c.engine.SetTrustedProxies([]string{"40.40.40.40", "30.30.30.30", "20.20.20.20"}) From 464535ff94799b012f90f15edcffd32e4abe32fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 07:55:14 +0800 Subject: [PATCH 605/912] Bump github.com/goccy/go-json from 0.7.9 to 0.7.10 (#2905) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c03439a..c25eecf 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.9.0 - github.com/goccy/go-json v0.7.9 + github.com/goccy/go-json v0.7.10 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 7bf71f9..51497a0 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.7.9 h1:mSp3uo1tr6MXQTYopSNhHTUnJhd2zQ4Yk+HdJZP+ZRY= -github.com/goccy/go-json v0.7.9/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.7.10 h1:ulhbuNe1JqE68nMRXXTJRrUu0uhouf0VevLINxQq4Ec= +github.com/goccy/go-json v0.7.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From 34ae7777cada2ab0f0ca650363fef7134d7ad74f Mon Sep 17 00:00:00 2001 From: Allen Ren Date: Fri, 22 Oct 2021 21:26:14 +0800 Subject: [PATCH 606/912] README.md: fix a typo (#2913) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62094ff..0fa78ec 100644 --- a/README.md +++ b/README.md @@ -2000,7 +2000,7 @@ 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 { + if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil { c.String(http.StatusOK, `the body should be formA`) // At this time, it reuses body stored in the context. } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { From 3fe928994b7ba7ed16b0c5114ca82a9dafa7a974 Mon Sep 17 00:00:00 2001 From: Zhu Xi Date: Sat, 23 Oct 2021 11:58:57 +0800 Subject: [PATCH 607/912] Update the code logic for latestNode in tree.go (#2897) --- context.go | 6 ++- gin.go | 12 +++-- gin_integration_test.go | 15 ++++++ tree.go | 117 ++++++++++++++++++++++++++-------------- tree_test.go | 22 +++++--- 5 files changed, 120 insertions(+), 52 deletions(-) diff --git a/context.go b/context.go index bea95cc..bc2c38e 100644 --- a/context.go +++ b/context.go @@ -55,8 +55,9 @@ type Context struct { index int8 fullPath string - engine *Engine - params *Params + engine *Engine + params *Params + skippedNodes *[]skippedNode // This mutex protect Keys map mu sync.RWMutex @@ -99,6 +100,7 @@ func (c *Context) reset() { c.queryCache = nil c.formCache = nil *c.params = (*c.params)[:0] + *c.skippedNodes = (*c.skippedNodes)[:0] } // Copy returns a copy of the current context that can be safely used outside the request's scope. diff --git a/gin.go b/gin.go index af83161..1774717 100644 --- a/gin.go +++ b/gin.go @@ -144,6 +144,7 @@ type Engine struct { pool sync.Pool trees methodTrees maxParams uint16 + maxSections uint16 trustedProxies []string trustedCIDRs []*net.IPNet } @@ -200,7 +201,8 @@ func Default() *Engine { func (engine *Engine) allocateContext() *Context { v := make(Params, 0, engine.maxParams) - return &Context{engine: engine, params: &v} + skippedNodes := make([]skippedNode, 0, engine.maxSections) + return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes} } // Delims sets template left and right delims and returns a Engine instance. @@ -306,6 +308,10 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { if paramsCount := countParams(path); paramsCount > engine.maxParams { engine.maxParams = paramsCount } + + if sectionsCount := countSections(path); sectionsCount > engine.maxSections { + engine.maxSections = sectionsCount + } } // Routes returns a slice of registered routes, including some useful information, such as: @@ -539,7 +545,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { } root := t[i].root // Find route in tree - value := root.getValue(rPath, c.params, unescape) + value := root.getValue(rPath, c.params, c.skippedNodes, unescape) if value.params != nil { c.Params = *value.params } @@ -567,7 +573,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { if tree.method == httpMethod { continue } - if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { + if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return diff --git a/gin_integration_test.go b/gin_integration_test.go index 0b67b54..8c22e7b 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -408,8 +408,13 @@ func TestTreeRunDynamicRouting(t *testing.T) { router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") }) router.GET("/", func(c *Context) { c.String(http.StatusOK, "home") }) router.GET("/:cc", func(c *Context) { c.String(http.StatusOK, "/:cc") }) + router.GET("/c1/:dd/e", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e") }) + router.GET("/c1/:dd/e1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e1") }) + router.GET("/c1/:dd/f1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f1") }) + router.GET("/c1/:dd/f2", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f2") }) router.GET("/:cc/cc", func(c *Context) { c.String(http.StatusOK, "/:cc/cc") }) router.GET("/:cc/:dd/ee", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/ee") }) + router.GET("/:cc/:dd/f", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/f") }) router.GET("/:cc/:dd/:ee/ff", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/ff") }) router.GET("/:cc/:dd/:ee/:ff/gg", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/gg") }) router.GET("/:cc/:dd/:ee/:ff/:gg/hh", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/:gg/hh") }) @@ -446,6 +451,10 @@ func TestTreeRunDynamicRouting(t *testing.T) { testRequest(t, ts.URL+"/all", "", "/:cc") testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc") testRequest(t, ts.URL+"/a/cc", "", "/:cc/cc") + testRequest(t, ts.URL+"/c1/d/e", "", "/c1/:dd/e") + testRequest(t, ts.URL+"/c1/d/e1", "", "/c1/:dd/e1") + testRequest(t, ts.URL+"/c1/d/ee", "", "/:cc/:dd/ee") + testRequest(t, ts.URL+"/c1/d/f", "", "/:cc/:dd/f") testRequest(t, ts.URL+"/c/d/ee", "", "/:cc/:dd/ee") testRequest(t, ts.URL+"/c/d/e/ff", "", "/:cc/:dd/:ee/ff") testRequest(t, ts.URL+"/c/d/e/f/gg", "", "/:cc/:dd/:ee/:ff/gg") @@ -528,6 +537,12 @@ func TestTreeRunDynamicRouting(t *testing.T) { testRequest(t, ts.URL+"/get/abc/123abf/testss", "", "/get/abc/123abf/:param") testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param") // 404 not found + testRequest(t, ts.URL+"/c/d/e", "404 Not Found") + testRequest(t, ts.URL+"/c/d/e1", "404 Not Found") + testRequest(t, ts.URL+"/c/d/eee", "404 Not Found") + testRequest(t, ts.URL+"/c1/d/eee", "404 Not Found") + testRequest(t, ts.URL+"/c1/d/e2", "404 Not Found") + testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh1", "404 Not Found") testRequest(t, ts.URL+"/a/dd", "404 Not Found") testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found") testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found") diff --git a/tree.go b/tree.go index fb0a593..c8a7548 100644 --- a/tree.go +++ b/tree.go @@ -17,6 +17,7 @@ import ( var ( strColon = []byte(":") strStar = []byte("*") + strSlash = []byte("/") ) // Param is a single URL parameter, consisting of a key and a value. @@ -98,6 +99,11 @@ func countParams(path string) uint16 { return n } +func countSections(path string) uint16 { + s := bytesconv.StringToBytes(path) + return uint16(bytes.Count(s, strSlash)) +} + type nodeType uint8 const ( @@ -393,16 +399,19 @@ type nodeValue struct { fullPath string } +type skippedNode struct { + path string + node *node + paramsCount int16 +} + // Returns the handle registered with the given path (key). The values of // wildcards are saved to a map. // If no handle can be found, a TSR (trailing slash redirect) recommendation is // made if a handle exists with an extra (without the) trailing slash for the // given path. -func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) { - var ( - skippedPath string - latestNode = n // Caching the latest node - ) +func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) { + var globalParamsCount int16 walk: // Outer loop for walking the tree for { @@ -417,15 +426,20 @@ walk: // Outer loop for walking the tree if c == idxc { // strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild if n.wildChild { - skippedPath = prefix + path - latestNode = &node{ - path: n.path, - wildChild: n.wildChild, - nType: n.nType, - priority: n.priority, - children: n.children, - handlers: n.handlers, - fullPath: n.fullPath, + index := len(*skippedNodes) + *skippedNodes = (*skippedNodes)[:index+1] + (*skippedNodes)[index] = skippedNode{ + path: prefix + path, + node: &node{ + path: n.path, + wildChild: n.wildChild, + nType: n.nType, + priority: n.priority, + children: n.children, + handlers: n.handlers, + fullPath: n.fullPath, + }, + paramsCount: globalParamsCount, } } @@ -434,10 +448,22 @@ walk: // Outer loop for walking the tree } } // If the path at the end of the loop is not equal to '/' and the current node has no child nodes - // the current node needs to be equal to the latest matching node - matched := path != "/" && !n.wildChild - if matched { - n = latestNode + // the current node needs to roll back to last vaild skippedNode + + if path != "/" && !n.wildChild { + for l := len(*skippedNodes); l > 0; { + skippedNode := (*skippedNodes)[l-1] + *skippedNodes = (*skippedNodes)[:l-1] + if strings.HasSuffix(skippedNode.path, path) { + path = skippedNode.path + n = skippedNode.node + if value.params != nil { + *value.params = (*value.params)[:skippedNode.paramsCount] + } + globalParamsCount = skippedNode.paramsCount + continue walk + } + } } // If there is no wildcard pattern, recommend a redirection @@ -451,18 +477,12 @@ walk: // Outer loop for walking the tree // Handle wildcard child, which is always at the end of the array n = n.children[len(n.children)-1] + globalParamsCount++ switch n.nType { case param: // fix truncate the parameter // tree_test.go line: 204 - if matched { - path = prefix + path - // The saved path is used after the prefix route is intercepted by matching - if n.indices == "/" { - path = skippedPath[1:] - } - } // Find param end (either '/' or path end) end := 0 @@ -548,9 +568,22 @@ walk: // Outer loop for walking the tree if path == prefix { // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node - // the current node needs to be equal to the latest matching node - if latestNode.wildChild && n.handlers == nil && path != "/" { - n = latestNode.children[len(latestNode.children)-1] + // the current node needs to roll back to last vaild skippedNode + if n.handlers == nil && path != "/" { + for l := len(*skippedNodes); l > 0; { + skippedNode := (*skippedNodes)[l-1] + *skippedNodes = (*skippedNodes)[:l-1] + if strings.HasSuffix(skippedNode.path, path) { + path = skippedNode.path + n = skippedNode.node + if value.params != nil { + *value.params = (*value.params)[:skippedNode.paramsCount] + } + globalParamsCount = skippedNode.paramsCount + continue walk + } + } + // n = latestNode.children[len(latestNode.children)-1] } // We should have reached the node containing the handle. // Check if this node has a handle registered. @@ -581,19 +614,21 @@ walk: // Outer loop for walking the tree return } - if path != "/" && len(skippedPath) > 0 && strings.HasSuffix(skippedPath, path) { - path = skippedPath - // Reduce the number of cycles - n, latestNode = latestNode, n - // skippedPath cannot execute - // example: - // * /:cc/cc - // call /a/cc expectations:match/200 Actual:match/200 - // call /a/dd expectations:unmatch/404 Actual: panic - // call /addr/dd/aa expectations:unmatch/404 Actual: panic - // skippedPath: It can only be executed if the secondary route is not found - skippedPath = "" - continue walk + // roll back to last vaild skippedNode + if path != "/" { + for l := len(*skippedNodes); l > 0; { + skippedNode := (*skippedNodes)[l-1] + *skippedNodes = (*skippedNodes)[:l-1] + if strings.HasSuffix(skippedNode.path, path) { + path = skippedNode.path + n = skippedNode.node + if value.params != nil { + *value.params = (*value.params)[:skippedNode.paramsCount] + } + globalParamsCount = skippedNode.paramsCount + continue walk + } + } } // Nothing found. We can recommend to redirect to the same URL with an diff --git a/tree_test.go b/tree_test.go index 8ae5b7d..49b3b57 100644 --- a/tree_test.go +++ b/tree_test.go @@ -33,6 +33,11 @@ func getParams() *Params { return &ps } +func getSkippedNodes() *[]skippedNode { + ps := make([]skippedNode, 0, 20) + return &ps +} + func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) { unescape := false if len(unescapes) >= 1 { @@ -40,7 +45,7 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes .. } for _, request := range requests { - value := tree.getValue(request.path, getParams(), unescape) + value := tree.getValue(request.path, getParams(), getSkippedNodes(), unescape) if value.handlers == nil { if !request.nilHandler { @@ -157,6 +162,8 @@ func TestTreeWildcard(t *testing.T) { "/aa/*xx", "/ab/*xx", "/:cc", + "/c1/:dd/e", + "/c1/:dd/e1", "/:cc/cc", "/:cc/:dd/ee", "/:cc/:dd/:ee/ff", @@ -238,6 +245,9 @@ func TestTreeWildcard(t *testing.T) { {"/alldd", false, "/:cc", Params{Param{Key: "cc", Value: "alldd"}}}, {"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "all"}}}, {"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "a"}}}, + {"/c1/d/e", false, "/c1/:dd/e", Params{Param{Key: "dd", Value: "d"}}}, + {"/c1/d/e1", false, "/c1/:dd/e1", Params{Param{Key: "dd", Value: "d"}}}, + {"/c1/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c1"}, Param{Key: "dd", Value: "d"}}}, {"/cc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "cc"}}}, {"/ccc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ccc"}}}, {"/deedwjfs/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "deedwjfs"}}}, @@ -605,7 +615,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/doc/", } for _, route := range tsrRoutes { - value := tree.getValue(route, nil, false) + value := tree.getValue(route, nil, getSkippedNodes(), false) if value.handlers != nil { t.Fatalf("non-nil handler for TSR route '%s", route) } else if !value.tsr { @@ -622,7 +632,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/api/world/abc", } for _, route := range noTsrRoutes { - value := tree.getValue(route, nil, false) + value := tree.getValue(route, nil, getSkippedNodes(), false) if value.handlers != nil { t.Fatalf("non-nil handler for No-TSR route '%s", route) } else if value.tsr { @@ -641,7 +651,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { t.Fatalf("panic inserting test route: %v", recv) } - value := tree.getValue("/", nil, false) + value := tree.getValue("/", nil, getSkippedNodes(), false) if value.handlers != nil { t.Fatalf("non-nil handler") } else if value.tsr { @@ -821,7 +831,7 @@ func TestTreeInvalidNodeType(t *testing.T) { // normal lookup recv := catchPanic(func() { - tree.getValue("/test", nil, false) + tree.getValue("/test", nil, getSkippedNodes(), false) }) if rs, ok := recv.(string); !ok || rs != panicMsg { t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv) @@ -846,7 +856,7 @@ func TestTreeInvalidParamsType(t *testing.T) { params := make(Params, 0) // try to trigger slice bounds out of range with capacity 0 - tree.getValue("/test", ¶ms, false) + tree.getValue("/test", ¶ms, getSkippedNodes(), false) } func TestTreeWildcardConflictEx(t *testing.T) { From 2d3d6d2f1355f21a77f8ef6cf45a5bc531c8c935 Mon Sep 17 00:00:00 2001 From: Notealot <714804968@qq.com> Date: Sun, 24 Oct 2021 08:34:03 +0800 Subject: [PATCH 608/912] Provide custom options of TrustedPlatform for another CDN services (#2906) * refine TrustedPlatform and docs * refactor for switch * refactor switch to if statement --- README.md | 29 +++++++++++++++++++++++++++++ context.go | 14 +++++--------- context_test.go | 14 +++++++++++++- gin.go | 4 ++-- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0fa78ec..cad746d 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [http2 server push](#http2-server-push) - [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Set and get a cookie](#set-and-get-a-cookie) + - [Don't trust all proxies](#don't-trust-all-proxies) - [Testing](#testing) - [Users](#users) @@ -2236,6 +2237,34 @@ func main() { } ``` +**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` +to skip TrustedProxies check, it has a higher priority than TrustedProxies. +Look at the example below: +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + // Use predefined header gin.PlatformXXX + router.TrustedPlatform = gin.PlatformGoogleAppEngine + // Or set your own trusted request header for another trusted proxy service + // Don't set it to any suspect request header, it's unsafe + router.TrustedPlatform = "X-CDN-IP" + + router.GET("/", func(c *gin.Context) { + // If you set TrustedPlatform, ClientIP() will resolve the + // corresponding header and return IP directly + fmt.Printf("ClientIP: %s\n", c.ClientIP()) + }) + router.Run() +} +``` + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/context.go b/context.go index bc2c38e..58f38c8 100644 --- a/context.go +++ b/context.go @@ -735,20 +735,16 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e return bb.BindBody(body, obj) } -// ClientIP implements a best effort algorithm to return the real client IP. +// ClientIP implements one best effort algorithm to return the real client IP. // It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. // If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). // If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy, // the remote IP (coming form Request.RemoteAddr) is returned. func (c *Context) ClientIP() string { - // Check if we're running on a trusted platform - switch c.engine.TrustedPlatform { - case PlatformGoogleAppEngine: - if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { - return addr - } - case PlatformCloudflare: - if addr := c.requestHeader("CF-Connecting-IP"); addr != "" { + // Check if we're running on a trusted platform, continue running backwards if error + if c.engine.TrustedPlatform != "" { + // Developers can define their own header of Trusted Platform or use predefined constants + if addr := c.requestHeader(c.engine.TrustedPlatform); addr != "" { return addr } } diff --git a/context_test.go b/context_test.go index e9fe88f..c286c0f 100644 --- a/context_test.go +++ b/context_test.go @@ -1458,8 +1458,20 @@ func TestContextClientIP(t *testing.T) { c.engine.TrustedPlatform = PlatformGoogleAppEngine assert.Equal(t, "50.50.50.50", c.ClientIP()) - // Test the legacy flag + // Use custom TrustedPlatform header + c.engine.TrustedPlatform = "X-CDN-IP" + c.Request.Header.Set("X-CDN-IP", "80.80.80.80") + assert.Equal(t, "80.80.80.80", c.ClientIP()) + // wrong header + c.engine.TrustedPlatform = "X-Wrong-Header" + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + c.Request.Header.Del("X-CDN-IP") + // TrustedPlatform is empty c.engine.TrustedPlatform = "" + assert.Equal(t, "40.40.40.40", c.ClientIP()) + + // Test the legacy flag c.engine.AppEngine = true assert.Equal(t, "50.50.50.50", c.ClientIP()) c.engine.AppEngine = false diff --git a/gin.go b/gin.go index 1774717..1d9c9fe 100644 --- a/gin.go +++ b/gin.go @@ -59,10 +59,10 @@ type RoutesInfo []RouteInfo const ( // When running on Google App Engine. Trust X-Appengine-Remote-Addr // for determining the client's IP - PlatformGoogleAppEngine = "google-app-engine" + PlatformGoogleAppEngine = "X-Appengine-Remote-Addr" // When using Cloudflare's CDN. Trust CF-Connecting-IP for determining // the client's IP - PlatformCloudflare = "cloudflare" + PlatformCloudflare = "CF-Connecting-IP" ) // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. From eb75ce0ff5d64b269f2a25e83952e374e762c4b6 Mon Sep 17 00:00:00 2001 From: heige Date: Sun, 24 Oct 2021 09:31:13 +0800 Subject: [PATCH 609/912] adjust the routergroup Any method (#2701) --- routergroup.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/routergroup.go b/routergroup.go index bb24bd5..27d7aad 100644 --- a/routergroup.go +++ b/routergroup.go @@ -14,6 +14,13 @@ import ( var ( // reg match english letters for http method name regEnLetter = regexp.MustCompile("^[A-Z]+$") + + // anyMethods for RouterGroup Any method + anyMethods = []string{ + http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, + http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect, + http.MethodTrace, + } ) // IRouter defines all router handle interface includes single and group router. @@ -136,15 +143,10 @@ func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRo // Any registers a route that matches all the HTTP methods. // GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { - group.handle(http.MethodGet, relativePath, handlers) - group.handle(http.MethodPost, relativePath, handlers) - group.handle(http.MethodPut, relativePath, handlers) - group.handle(http.MethodPatch, relativePath, handlers) - group.handle(http.MethodHead, relativePath, handlers) - group.handle(http.MethodOptions, relativePath, handlers) - group.handle(http.MethodDelete, relativePath, handlers) - group.handle(http.MethodConnect, relativePath, handlers) - group.handle(http.MethodTrace, relativePath, handlers) + for _, method := range anyMethods { + group.handle(method, relativePath, handlers) + } + return group.returnObj() } From 1c2aa59b20c8cff5b3c601708afe22100eac25e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B8=82=E6=B0=91233?= Date: Tue, 26 Oct 2021 18:15:29 +0800 Subject: [PATCH 610/912] fix the misplacement of adding slashes (#2847) --- routes_test.go | 15 +++++++++++++++ tree.go | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/routes_test.go b/routes_test.go index 036fa1c..ffe3469 100644 --- a/routes_test.go +++ b/routes_test.go @@ -481,6 +481,21 @@ func TestRouterNotFound(t *testing.T) { router.GET("/a", func(c *Context) {}) w = performRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusNotFound, w.Code) + + // Reproduction test for the bug of issue #2843 + router = New() + router.NoRoute(func(c *Context) { + if c.Request.RequestURI == "/login" { + c.String(200, "login") + } + }) + router.GET("/logout", func(c *Context) { + c.String(200, "logout") + }) + w = performRequest(router, http.MethodGet, "/login") + assert.Equal(t, "login", w.Body.String()) + w = performRequest(router, http.MethodGet, "/logout") + assert.Equal(t, "logout", w.Body.String()) } func TestRouterStaticFSNotFound(t *testing.T) { diff --git a/tree.go b/tree.go index c8a7548..a0d9835 100644 --- a/tree.go +++ b/tree.go @@ -634,7 +634,8 @@ walk: // Outer loop for walking the tree // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path value.tsr = path == "/" || - (len(prefix) == len(path)+1 && n.handlers != nil) + (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && + path == prefix[:len(prefix)-1] && n.handlers != nil) return } } From d4e72a17f7771d929a35bc8bb561ee24676dac1a Mon Sep 17 00:00:00 2001 From: Tommy Chu Date: Sun, 31 Oct 2021 02:23:08 +0100 Subject: [PATCH 611/912] Fix typo (#2926) --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 0f276c1..3418cbc 100644 --- a/errors.go +++ b/errors.go @@ -122,7 +122,7 @@ func (a errorMsgs) Last() *Error { return nil } -// Errors returns an array will all the error messages. +// Errors returns an array with all the error messages. // Example: // c.Error(errors.New("first")) // c.Error(errors.New("second")) From cbdd47a7e108217d4672f0e24d423b632a8cd900 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sun, 31 Oct 2021 20:21:37 -0400 Subject: [PATCH 612/912] fix tsr with mixed static and wildcard paths (#2924) --- tree.go | 48 ++++++++++++++++++++++++------------------------ tree_test.go | 27 +++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/tree.go b/tree.go index a0d9835..2f0de1a 100644 --- a/tree.go +++ b/tree.go @@ -447,27 +447,26 @@ walk: // Outer loop for walking the tree continue walk } } - // If the path at the end of the loop is not equal to '/' and the current node has no child nodes - // the current node needs to roll back to last vaild skippedNode - - if path != "/" && !n.wildChild { - for l := len(*skippedNodes); l > 0; { - skippedNode := (*skippedNodes)[l-1] - *skippedNodes = (*skippedNodes)[:l-1] - if strings.HasSuffix(skippedNode.path, path) { - path = skippedNode.path - n = skippedNode.node - if value.params != nil { - *value.params = (*value.params)[:skippedNode.paramsCount] + + if !n.wildChild { + // If the path at the end of the loop is not equal to '/' and the current node has no child nodes + // the current node needs to roll back to last vaild skippedNode + if path != "/" { + for l := len(*skippedNodes); l > 0; { + skippedNode := (*skippedNodes)[l-1] + *skippedNodes = (*skippedNodes)[:l-1] + if strings.HasSuffix(skippedNode.path, path) { + path = skippedNode.path + n = skippedNode.node + if value.params != nil { + *value.params = (*value.params)[:skippedNode.paramsCount] + } + globalParamsCount = skippedNode.paramsCount + continue walk } - globalParamsCount = skippedNode.paramsCount - continue walk } } - } - // If there is no wildcard pattern, recommend a redirection - if !n.wildChild { // Nothing found. // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. @@ -614,8 +613,14 @@ walk: // Outer loop for walking the tree return } - // roll back to last vaild skippedNode - if path != "/" { + // Nothing found. We can recommend to redirect to the same URL with an + // extra trailing slash if a leaf exists for that path + value.tsr = path == "/" || + (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && + path == prefix[:len(prefix)-1] && n.handlers != nil) + + // roll back to last valid skippedNode + if !value.tsr && path != "/" { for l := len(*skippedNodes); l > 0; { skippedNode := (*skippedNodes)[l-1] *skippedNodes = (*skippedNodes)[:l-1] @@ -631,11 +636,6 @@ walk: // Outer loop for walking the tree } } - // Nothing found. We can recommend to redirect to the same URL with an - // extra trailing slash if a leaf exists for that path - value.tsr = path == "/" || - (len(prefix) == len(path)+1 && prefix[len(path)] == '/' && - path == prefix[:len(prefix)-1] && n.handlers != nil) return } } diff --git a/tree_test.go b/tree_test.go index 49b3b57..c372339 100644 --- a/tree_test.go +++ b/tree_test.go @@ -587,7 +587,14 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/doc/go1.html", "/no/a", "/no/b", - "/api/hello/:name", + "/api/:page/:name", + "/api/hello/:name/bar/", + "/api/bar/:name", + "/api/baz/foo", + "/api/baz/foo/bar", + "/blog/:p", + "/posts/:b/:c", + "/posts/b/:c/d/", } for _, route := range routes { recv := catchPanic(func() { @@ -613,7 +620,19 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/admin/config/", "/admin/config/permissions/", "/doc/", + "/admin/static/", + "/admin/cfg/", + "/admin/cfg/users/", + "/api/hello/x/bar", + "/api/baz/foo/", + "/api/baz/bax/", + "/api/bar/huh/", + "/api/baz/foo/bar/", + "/api/world/abc/", + "/blog/pp/", + "/posts/b/c/d", } + for _, route := range tsrRoutes { value := tree.getValue(route, nil, getSkippedNodes(), false) if value.handlers != nil { @@ -629,7 +648,11 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/no/", "/_", "/_/", - "/api/world/abc", + "/api", + "/api/", + "/api/hello/x/foo", + "/api/baz/foo/bad", + "/foo/p/p", } for _, route := range noTsrRoutes { value := tree.getValue(route, nil, getSkippedNodes(), false) From efa3175007d2d4e392bc44e11a6419431686412e Mon Sep 17 00:00:00 2001 From: Quentin ROYER Date: Mon, 1 Nov 2021 01:33:38 +0100 Subject: [PATCH 613/912] Update version.go (#2923) --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 535bfc8..b9110ad 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.7.3" +const Version = "v1.7.4" From 89a159bdd92c15374764530ab93477a1740502c1 Mon Sep 17 00:00:00 2001 From: Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> Date: Wed, 3 Nov 2021 17:13:24 +0300 Subject: [PATCH 614/912] Bump golangci-lint version (#2929) --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 15c2530..c25a909 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -21,7 +21,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.42.1 + version: v1.43.0 args: --verbose test: needs: lint From 6aee45608da726e4d7e51f558728e0eb90ac6790 Mon Sep 17 00:00:00 2001 From: zero11-0203 <93071220+zero11-0203@users.noreply.github.com> Date: Thu, 11 Nov 2021 08:07:55 +0800 Subject: [PATCH 615/912] Fix grammar (#2933) --- context.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 58f38c8..2aa91e1 100644 --- a/context.go +++ b/context.go @@ -252,7 +252,7 @@ func (c *Context) Set(key string, value interface{}) { } // Get returns the value for the given key, ie: (value, true). -// If the value does not exists it returns (nil, false) +// If the value does not exist it returns (nil, false) func (c *Context) Get(key string) (value interface{}, exists bool) { c.mu.RLock() value, exists = c.Keys[key] @@ -602,7 +602,7 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error } // Bind checks the Content-Type to select a binding engine automatically, -// Depending the "Content-Type" header different bindings are used: +// Depending on the "Content-Type" header different bindings are used: // "application/json" --> JSON binding // "application/xml" --> XML binding // otherwise --> returns an error. @@ -661,7 +661,7 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { } // ShouldBind checks the Content-Type to select a binding engine automatically, -// Depending the "Content-Type" header different bindings are used: +// Depending on the "Content-Type" header different bindings are used: // "application/json" --> JSON binding // "application/xml" --> XML binding // otherwise --> returns an error @@ -863,7 +863,7 @@ func (c *Context) Status(code int) { c.Writer.WriteHeader(code) } -// Header is a intelligent shortcut for c.Writer.Header().Set(key, value). +// Header is an intelligent shortcut for c.Writer.Header().Set(key, value). // It writes a header in the response. // If value == "", this method removes the header `c.Writer.Header().Del(key)` func (c *Context) Header(key, value string) { @@ -946,7 +946,7 @@ func (c *Context) HTML(code int, name string, obj interface{}) { // IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body. // It also sets the Content-Type as "application/json". -// WARNING: we recommend to use this only for development purposes since printing pretty JSON is +// WARNING: we recommend using this only for development purposes since printing pretty JSON is // more CPU and bandwidth consuming. Use Context.JSON() instead. func (c *Context) IndentedJSON(code int, obj interface{}) { c.Render(code, render.IndentedJSON{Data: obj}) @@ -1010,7 +1010,7 @@ func (c *Context) String(code int, format string, values ...interface{}) { c.Render(code, render.String{Format: format, Data: values}) } -// Redirect returns a HTTP redirect to the specific location. +// Redirect returns an HTTP redirect to the specific location. func (c *Context) Redirect(code int, location string) { c.Render(-1, render.Redirect{ Code: code, From 57ede9c95abb4bc39f471b101181eb06938b5d7f Mon Sep 17 00:00:00 2001 From: edebernis Date: Sun, 21 Nov 2021 14:45:03 +0100 Subject: [PATCH 616/912] Export struct sliceValidateError to allow error casting and rename it as (#2777) SliceValidationError Co-authored-by: Emeric de Bernis --- binding/default_validator.go | 8 ++++---- binding/default_validator_benchmark_test.go | 4 ++-- binding/default_validator_test.go | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index 87fc4c6..bd8764b 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -18,10 +18,10 @@ type defaultValidator struct { validate *validator.Validate } -type sliceValidateError []error +type SliceValidationError []error -// Error concatenates all error elements in sliceValidateError into a single string separated by \n. -func (err sliceValidateError) Error() string { +// Error concatenates all error elements in SliceValidationError into a single string separated by \n. +func (err SliceValidationError) Error() string { n := len(err) switch n { case 0: @@ -59,7 +59,7 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error { return v.validateStruct(obj) case reflect.Slice, reflect.Array: count := value.Len() - validateRet := make(sliceValidateError, 0) + validateRet := make(SliceValidationError, 0) for i := 0; i < count; i++ { if err := v.ValidateStruct(value.Index(i).Interface()); err != nil { validateRet = append(validateRet, err) diff --git a/binding/default_validator_benchmark_test.go b/binding/default_validator_benchmark_test.go index 839cf71..8d62836 100644 --- a/binding/default_validator_benchmark_test.go +++ b/binding/default_validator_benchmark_test.go @@ -6,10 +6,10 @@ import ( "testing" ) -func BenchmarkSliceValidateError(b *testing.B) { +func BenchmarkSliceValidationError(b *testing.B) { const size int = 100 for i := 0; i < b.N; i++ { - e := make(sliceValidateError, size) + e := make(SliceValidationError, size) for j := 0; j < size; j++ { e[j] = errors.New(strconv.Itoa(j)) } diff --git a/binding/default_validator_test.go b/binding/default_validator_test.go index e9debe5..ff13010 100644 --- a/binding/default_validator_test.go +++ b/binding/default_validator_test.go @@ -9,24 +9,24 @@ import ( "testing" ) -func TestSliceValidateError(t *testing.T) { +func TestSliceValidationError(t *testing.T) { tests := []struct { name string - err sliceValidateError + err SliceValidationError want string }{ - {"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"}, - {"has zero elements", sliceValidateError{}, ""}, - {"has one element", sliceValidateError{errors.New("test one error")}, "[0]: test one error"}, + {"has nil elements", SliceValidationError{errors.New("test error"), nil}, "[0]: test error"}, + {"has zero elements", SliceValidationError{}, ""}, + {"has one element", SliceValidationError{errors.New("test one error")}, "[0]: test one error"}, {"has two elements", - sliceValidateError{ + SliceValidationError{ errors.New("first error"), errors.New("second error"), }, "[0]: first error\n[1]: second error", }, {"has many elements", - sliceValidateError{ + SliceValidationError{ errors.New("first error"), errors.New("second error"), nil, @@ -40,7 +40,7 @@ func TestSliceValidateError(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.err.Error(); got != tt.want { - t.Errorf("sliceValidateError.Error() = %v, want %v", got, tt.want) + t.Errorf("SliceValidationError.Error() = %v, want %v", got, tt.want) } }) } From 4d7c4ec36ff51c5c3d500f0e77c9e05e5d303a06 Mon Sep 17 00:00:00 2001 From: Notealot <714804968@qq.com> Date: Wed, 24 Nov 2021 21:52:52 +0800 Subject: [PATCH 617/912] chore(docs): Bump to v1.7.7 (#2952) * Perfect TrustedProxies feature * some typo fix * fix some typo * bump to v1.7.6 * remove 'retract' * Revert "remove 'retract'" This reverts commit 3fc2ab44b365d0b14f56ba1a284c52bd88c097b5. * Update CHANGELOG.md * Bump to v1.7.7 preparations --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ go.mod | 2 ++ version.go | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 308af74..4c806a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Gin ChangeLog +## Gin v1.7.7 + +### BUGFIXES + +* Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862). +* Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878). +* Tree: fixed the misplacement of adding slashes [#2847](https://github.com/gin-gonic/gin/pull/2847), closed issue [#2843](https://github.com/gin-gonic/gin/issues/2843). +* Tree: fixed tsr with mixed static and wildcard paths [#2924](https://github.com/gin-gonic/gin/pull/2924), closed issue [#2918](https://github.com/gin-gonic/gin/issues/2918). + +### ENHANCEMENTS + +* TrustedProxies: make it backward-compatible [#2887](https://github.com/gin-gonic/gin/pull/2887), closed issue [#2819](https://github.com/gin-gonic/gin/issues/2819). +* TrustedPlatform: provide custom options for another CDN services [#2906](https://github.com/gin-gonic/gin/pull/2906). + +### DOCS + +* NoMethod: added usage annotation ([#2832](https://github.com/gin-gonic/gin/pull/2832#issuecomment-929954463)). + +## Gin v1.7.6 + +### BUGFIXES + +* bump new release to fix v1.7.5 release error by using v1.7.4 codes. + +## Gin v1.7.4 + +### BUGFIXES + +* bump new release to fix checksum mismatch + ## Gin v1.7.3 ### BUGFIXES diff --git a/go.mod b/go.mod index c25eecf..8a5d720 100644 --- a/go.mod +++ b/go.mod @@ -13,3 +13,5 @@ require ( google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 ) + +retract v1.7.5 diff --git a/version.go b/version.go index b9110ad..4b69b9b 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.7.4" +const Version = "v1.7.7" From 823adfc91abfb2867512b4741a4bebfebfafab64 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Thu, 25 Nov 2021 18:12:08 +0800 Subject: [PATCH 618/912] fix: typo (#2958) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 1d9c9fe..21513c6 100644 --- a/gin.go +++ b/gin.go @@ -271,7 +271,7 @@ func (engine *Engine) NoMethod(handlers ...HandlerFunc) { engine.rebuild405Handlers() } -// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be +// Use attaches a global middleware to the router. ie. the middleware attached through Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { From ffb3b73430e8627e6ea934351930685e15c96619 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Fri, 26 Nov 2021 16:38:10 +0800 Subject: [PATCH 619/912] fix: description error (#2961) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 2aa91e1..a97dd40 100644 --- a/context.go +++ b/context.go @@ -1102,7 +1102,7 @@ type Negotiate struct { Data interface{} } -// Negotiate calls different Render according acceptable Accept format. +// Negotiate calls different Render according to acceptable Accept format. func (c *Context) Negotiate(code int, config Negotiate) { switch c.NegotiateFormat(config.Offered...) { case binding.MIMEJSON: From f068099d0d4b408c8ecbcbe1dc01d700bf5b2bc5 Mon Sep 17 00:00:00 2001 From: minarc Date: Sun, 28 Nov 2021 10:24:14 +0900 Subject: [PATCH 620/912] Update README.md (#2954) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cad746d..e3d6ab2 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [http2 server push](#http2-server-push) - [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Set and get a cookie](#set-and-get-a-cookie) - - [Don't trust all proxies](#don't-trust-all-proxies) + - [Don't trust all proxies](#dont-trust-all-proxies) - [Testing](#testing) - [Users](#users) From a06d546f5c2e853b07fde5a41b1fff102829b116 Mon Sep 17 00:00:00 2001 From: Serica <943914044@qq.com> Date: Sun, 28 Nov 2021 09:26:17 +0800 Subject: [PATCH 621/912] prettify error message for catch-all conflict with existing path segment (#2934) --- tree.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 2f0de1a..521d3ef 100644 --- a/tree.go +++ b/tree.go @@ -349,7 +349,12 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) } if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { - panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'") + pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0] + panic("catch-all wildcard '" + path + + "' in new path '" + fullPath + + "' conflicts with existing path segment '" + pathSeg + + "' in existing prefix '" + n.path + pathSeg + + "'") } // currently fixed width 1 for '/' From bc2417fc40a8f85fb36305dc05dc6ba3d09182a4 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Tue, 30 Nov 2021 08:36:36 +0800 Subject: [PATCH 622/912] fix: description error (#2968) --- context.go | 2 +- gin.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index a97dd40..30a8698 100644 --- a/context.go +++ b/context.go @@ -739,7 +739,7 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e // It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. // If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). // If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy, -// the remote IP (coming form Request.RemoteAddr) is returned. +// the remote IP (coming from Request.RemoteAddr) is returned. func (c *Context) ClientIP() string { // Check if we're running on a trusted platform, continue running backwards if error if c.engine.TrustedPlatform != "" { diff --git a/gin.go b/gin.go index 21513c6..d507b51 100644 --- a/gin.go +++ b/gin.go @@ -102,7 +102,7 @@ type Engine struct { // `(*gin.Context).Request.RemoteAddr`. ForwardedByClientIP bool - // DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.GoogleAppEngine` INSTEAD + // DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD // #726 #755 If enabled, it will trust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool From 830a63d244ad200a196116f71b6c3c1eda3a538f Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Thu, 2 Dec 2021 18:00:24 +0800 Subject: [PATCH 623/912] fix: typo (#2973) * fix: typo * fix: grammar error --- gin.go | 8 ++++---- logger.go | 4 ++-- recovery.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gin.go b/gin.go index d507b51..94de631 100644 --- a/gin.go +++ b/gin.go @@ -205,7 +205,7 @@ func (engine *Engine) allocateContext() *Context { return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes} } -// Delims sets template left and right delims and returns a Engine instance. +// Delims sets template left and right delims and returns an Engine instance. func (engine *Engine) Delims(left, right string) *Engine { engine.delims = render.Delims{Left: left, Right: right} return engine @@ -259,7 +259,7 @@ func (engine *Engine) SetFuncMap(funcMap template.FuncMap) { engine.FuncMap = funcMap } -// NoRoute adds handlers for NoRoute. It return a 404 code by default. +// NoRoute adds handlers for NoRoute. It returns a 404 code by default. func (engine *Engine) NoRoute(handlers ...HandlerFunc) { engine.noRoute = handlers engine.rebuild404Handlers() @@ -271,7 +271,7 @@ func (engine *Engine) NoMethod(handlers ...HandlerFunc) { engine.rebuild405Handlers() } -// Use attaches a global middleware to the router. ie. the middleware attached through Use() will be +// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { @@ -442,7 +442,7 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { } // RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests -// through the specified unix socket (ie. a file). +// through the specified unix socket (i.e. a file). // Note: this method will block the calling goroutine indefinitely unless an error happens. func (engine *Engine) RunUnix(file string) (err error) { debugPrint("Listening and serving HTTP on unix:/%s", file) diff --git a/logger.go b/logger.go index 22138a8..61b8454 100644 --- a/logger.go +++ b/logger.go @@ -70,7 +70,7 @@ type LogFormatterParams struct { Path string // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string - // isTerm shows whether does gin's output descriptor refers to a terminal. + // isTerm shows whether gin's output descriptor refers to a terminal. isTerm bool // BodySize is the size of the Response Body BodySize int @@ -178,7 +178,7 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc { } // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. -// By default gin.DefaultWriter = os.Stdout. +// By default, gin.DefaultWriter = os.Stdout. func Logger() HandlerFunc { return LoggerWithConfig(LoggerConfig{}) } diff --git a/recovery.go b/recovery.go index 39f1355..40eba3b 100644 --- a/recovery.go +++ b/recovery.go @@ -155,7 +155,7 @@ func function(pc uintptr) []byte { // runtime/debug.*T·ptrmethod // and want // *T.ptrmethod - // Also the package path might contains dot (e.g. code.google.com/...), + // Also the package path might contain dot (e.g. code.google.com/...), // so first eliminate the path prefix if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { name = name[lastSlash+1:] From 0be805a67503c5ad903c57940b7e9c19d88d522a Mon Sep 17 00:00:00 2001 From: Notealot <714804968@qq.com> Date: Fri, 3 Dec 2021 14:49:16 +0800 Subject: [PATCH 624/912] TrustedProxies: Add default IPv6 support and refactor (#2967) --- context.go | 52 ++++++++----------------------------------------- context_test.go | 10 +++++++++- gin.go | 51 ++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 49 deletions(-) diff --git a/context.go b/context.go index 30a8698..4fb3ae6 100644 --- a/context.go +++ b/context.go @@ -757,10 +757,14 @@ func (c *Context) ClientIP() string { } } - remoteIP, trusted := c.RemoteIP() + // It also checks if the remoteIP is a trusted proxy or not. + // In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks + // defined by Engine.SetTrustedProxies() + remoteIP := net.ParseIP(c.RemoteIP()) if remoteIP == nil { return "" } + trusted := c.engine.isTrustedProxy(remoteIP) if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil { for _, headerName := range c.engine.RemoteIPHeaders { @@ -773,53 +777,13 @@ func (c *Context) ClientIP() string { return remoteIP.String() } -func (e *Engine) isTrustedProxy(ip net.IP) bool { - if e.trustedCIDRs != nil { - for _, cidr := range e.trustedCIDRs { - if cidr.Contains(ip) { - return true - } - } - } - return false -} - // RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port). -// It also checks if the remoteIP is a trusted proxy or not. -// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks -// defined by Engine.SetTrustedProxies() -func (c *Context) RemoteIP() (net.IP, bool) { +func (c *Context) RemoteIP() string { ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)) if err != nil { - return nil, false - } - remoteIP := net.ParseIP(ip) - if remoteIP == nil { - return nil, false - } - - return remoteIP, c.engine.isTrustedProxy(remoteIP) -} - -func (e *Engine) validateHeader(header string) (clientIP string, valid bool) { - if header == "" { - return "", false - } - items := strings.Split(header, ",") - for i := len(items) - 1; i >= 0; i-- { - ipStr := strings.TrimSpace(items[i]) - ip := net.ParseIP(ipStr) - if ip == nil { - return "", false - } - - // X-Forwarded-For is appended by proxy - // Check IPs in reverse order and stop when find untrusted proxy - if (i == 0) || (!e.isTrustedProxy(ip)) { - return ipStr, true - } + return "" } - return + return ip } // ContentType returns the Content-Type header of the request. diff --git a/context_test.go b/context_test.go index c286c0f..4d002c2 100644 --- a/context_test.go +++ b/context_test.go @@ -12,6 +12,7 @@ import ( "html/template" "io" "mime/multipart" + "net" "net/http" "net/http/httptest" "os" @@ -1404,6 +1405,11 @@ func TestContextClientIP(t *testing.T) { // Tests exercising the TrustedProxies functionality resetContextForClientIPTests(c) + // IPv6 support + c.Request.RemoteAddr = "[::1]:12345" + assert.Equal(t, "20.20.20.20", c.ClientIP()) + + resetContextForClientIPTests(c) // No trusted proxies _ = c.engine.SetTrustedProxies([]string{}) c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"} @@ -1500,6 +1506,7 @@ func resetContextForClientIPTests(c *Context) { c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60") c.Request.RemoteAddr = " 40.40.40.40:42123 " c.engine.TrustedPlatform = "" + c.engine.trustedCIDRs = defaultTrustedCIDRs c.engine.AppEngine = false } @@ -2051,7 +2058,8 @@ func TestRemoteIPFail(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.RemoteAddr = "[:::]:80" - ip, trust := c.RemoteIP() + ip := net.ParseIP(c.RemoteIP()) + trust := c.engine.isTrustedProxy(ip) assert.Nil(t, ip) assert.False(t, trust) } diff --git a/gin.go b/gin.go index 94de631..45f4f2c 100644 --- a/gin.go +++ b/gin.go @@ -11,7 +11,6 @@ import ( "net/http" "os" "path" - "reflect" "strings" "sync" @@ -28,7 +27,16 @@ var ( var defaultPlatform string -var defaultTrustedCIDRs = []*net.IPNet{{IP: net.IP{0x0, 0x0, 0x0, 0x0}, Mask: net.IPMask{0x0, 0x0, 0x0, 0x0}}} // 0.0.0.0/0 +var defaultTrustedCIDRs = []*net.IPNet{ + { // 0.0.0.0/0 (IPv4) + IP: net.IP{0x0, 0x0, 0x0, 0x0}, + Mask: net.IPMask{0x0, 0x0, 0x0, 0x0}, + }, + { // ::/0 (IPv6) + IP: net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + }, +} // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -399,9 +407,9 @@ func (engine *Engine) SetTrustedProxies(trustedProxies []string) error { return engine.parseTrustedProxies() } -// isUnsafeTrustedProxies compares Engine.trustedCIDRs and defaultTrustedCIDRs, it's not safe if equal (returns true) +// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true) func (engine *Engine) isUnsafeTrustedProxies() bool { - return reflect.DeepEqual(engine.trustedCIDRs, defaultTrustedCIDRs) + return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::")) } // parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs @@ -411,6 +419,41 @@ func (engine *Engine) parseTrustedProxies() error { return err } +// isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs +func (engine *Engine) isTrustedProxy(ip net.IP) bool { + if engine.trustedCIDRs == nil { + return false + } + for _, cidr := range engine.trustedCIDRs { + if cidr.Contains(ip) { + return true + } + } + return false +} + +// validateHeader will parse X-Forwarded-For header and return the trusted client IP address +func (engine *Engine) validateHeader(header string) (clientIP string, valid bool) { + if header == "" { + return "", false + } + items := strings.Split(header, ",") + for i := len(items) - 1; i >= 0; i-- { + ipStr := strings.TrimSpace(items[i]) + ip := net.ParseIP(ipStr) + if ip == nil { + break + } + + // X-Forwarded-For is appended by proxy + // Check IPs in reverse order and stop when find untrusted proxy + if (i == 0) || (!engine.isTrustedProxy(ip)) { + return ipStr, true + } + } + return "", false +} + // parseIP parse a string representation of an IP and returns a net.IP with the // minimum byte representation or nil if input is invalid. func parseIP(ip string) net.IP { From 504ec594f8228ead9738ef42c13233eb42ed51d6 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Fri, 3 Dec 2021 14:49:51 +0800 Subject: [PATCH 625/912] fix:typo (#2975) --- gin.go | 2 +- ginS/gins.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gin.go b/gin.go index 45f4f2c..dac274c 100644 --- a/gin.go +++ b/gin.go @@ -160,7 +160,7 @@ type Engine struct { var _ IRouter = &Engine{} // New returns a new blank Engine instance without any middleware attached. -// By default the configuration is: +// By default, the configuration is: // - RedirectTrailingSlash: true // - RedirectFixedPath: false // - HandleMethodNotAllowed: false diff --git a/ginS/gins.go b/ginS/gins.go index 3080fd3..ed054bf 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -37,7 +37,7 @@ func SetHTMLTemplate(templ *template.Template) { engine().SetHTMLTemplate(templ) } -// NoRoute adds handlers for NoRoute. It return a 404 code by default. +// NoRoute adds handlers for NoRoute. It returns a 404 code by default. func NoRoute(handlers ...gin.HandlerFunc) { engine().NoRoute(handlers...) } @@ -118,7 +118,7 @@ func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes { return engine().StaticFS(relativePath, fs) } -// Use attaches a global middleware to the router. ie. the middlewares attached though Use() will be +// Use attaches a global middleware to the router. i.e. the middlewares attached though Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { @@ -145,7 +145,7 @@ func RunTLS(addr, certFile, keyFile string) (err error) { } // RunUnix attaches to a http.Server and starts listening and serving HTTP requests -// through the specified unix socket (ie. a file) +// through the specified unix socket (i.e. a file) // Note: this method will block the calling goroutine indefinitely unless an error happens. func RunUnix(file string) (err error) { return engine().RunUnix(file) From ba7e58989c4eefbaae2aaf4079e9135bafc9b090 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Sun, 5 Dec 2021 08:41:25 +0800 Subject: [PATCH 626/912] fix: typo (#2977) * fix:typo * fix: typo --- gin.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gin.go b/gin.go index dac274c..54e1a00 100644 --- a/gin.go +++ b/gin.go @@ -44,7 +44,7 @@ type HandlerFunc func(*Context) // HandlersChain defines a HandlerFunc array. type HandlersChain []HandlerFunc -// Last returns the last handler in the chain. ie. the last handler is the main one. +// Last returns the last handler in the chain. i.e. the last handler is the main one. func (c HandlersChain) Last() HandlerFunc { if length := len(c); length > 0 { return c[length-1] @@ -60,7 +60,7 @@ type RouteInfo struct { HandlerFunc HandlerFunc } -// RoutesInfo defines a RouteInfo array. +// RoutesInfo defines a RouteInfo slice. type RoutesInfo []RouteInfo // Trusted platforms @@ -556,9 +556,9 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { engine.pool.Put(c) } -// HandleContext re-enter a context that has been rewritten. +// HandleContext re-enters a context that has been rewritten. // This can be done by setting c.Request.URL.Path to your new target. -// Disclaimer: You can loop yourself to death with this, use wisely. +// Disclaimer: You can loop yourself to deal with this, use wisely. func (engine *Engine) HandleContext(c *Context) { oldIndexValue := c.index c.reset() From 3973c77263d74d5feb2a5923f2c0cbef866b3cc6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Dec 2021 08:09:09 +0800 Subject: [PATCH 627/912] Bump github.com/goccy/go-json from 0.7.10 to 0.8.1 (#2981) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.7.10 to 0.8.1. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.7.10...v0.8.1) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8a5d720..05b9760 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.9.0 - github.com/goccy/go-json v0.7.10 + github.com/goccy/go-json v0.8.1 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 51497a0..adacf5e 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.7.10 h1:ulhbuNe1JqE68nMRXXTJRrUu0uhouf0VevLINxQq4Ec= -github.com/goccy/go-json v0.7.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= +github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From e56d18f19d1f89c68de06e66272373287a4497d3 Mon Sep 17 00:00:00 2001 From: linzi <873804682@qq.com> Date: Sat, 11 Dec 2021 16:24:10 +0800 Subject: [PATCH 628/912] Update README.md (#2985) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e3d6ab2..f78a64e 100644 --- a/README.md +++ b/README.md @@ -385,7 +385,7 @@ func main() { router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // single file - file, _ := c.FormFile("file") + file, _ := c.FormFile("Filename") log.Println(file.Filename) // Upload the file to specific dst. From 7d189814cbf6d4c55e8a47b06234736cb1e03457 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Sun, 12 Dec 2021 13:30:33 +0800 Subject: [PATCH 629/912] fix: wrong when wildcard follows named param (#2983) --- tree.go | 2 +- tree_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 521d3ef..a30d349 100644 --- a/tree.go +++ b/tree.go @@ -535,7 +535,7 @@ walk: // Outer loop for walking the tree // No handle found. Check if a handle for this path + a // trailing slash exists for TSR recommendation n = n.children[0] - value.tsr = n.path == "/" && n.handlers != nil + value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/") } return diff --git a/tree_test.go b/tree_test.go index c372339..94c5338 100644 --- a/tree_test.go +++ b/tree_test.go @@ -595,6 +595,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/blog/:p", "/posts/:b/:c", "/posts/b/:c/d/", + "/vendor/:x/*y", } for _, route := range routes { recv := catchPanic(func() { @@ -631,6 +632,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/api/world/abc/", "/blog/pp/", "/posts/b/c/d", + "/vendor/x", } for _, route := range tsrRoutes { From fb5f0454178756d8040f9224092319febc3853b8 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Wed, 15 Dec 2021 23:27:23 +0800 Subject: [PATCH 630/912] fix: description error (#2986) --- AUTHORS.md | 5 +++++ gin.go | 2 +- mode.go | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index c634e6b..533204e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -109,6 +109,11 @@ People and companies, who have contributed, in alphabetical order. - Fix typo in comment +**@jincheng9 (Jincheng Zhang)** +- ★ support TSR when wildcard follows named param +- Fix errors and typos in comments + + **@joiggama (Ignacio Galindo)** - Add utf-8 charset header on renders diff --git a/gin.go b/gin.go index 54e1a00..9bd4c46 100644 --- a/gin.go +++ b/gin.go @@ -41,7 +41,7 @@ var defaultTrustedCIDRs = []*net.IPNet{ // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) -// HandlersChain defines a HandlerFunc array. +// HandlersChain defines a HandlerFunc slice. type HandlersChain []HandlerFunc // Last returns the last handler in the chain. i.e. the last handler is the main one. diff --git a/mode.go b/mode.go index 4d199df..1fb994b 100644 --- a/mode.go +++ b/mode.go @@ -88,7 +88,7 @@ func EnableJsonDecoderDisallowUnknownFields() { binding.EnableDecoderDisallowUnknownFields = true } -// Mode returns currently gin mode. +// Mode returns current gin mode. func Mode() string { return modeName } From 92a988d7613259f4edce0964a7244fc55a9ea015 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Sat, 18 Dec 2021 19:40:47 +0800 Subject: [PATCH 631/912] fix: description error (#2988) * fix:typo * fix: typo * fix: wrong when wildcard follows named param * fix: description error * update author list * fix:typo * fix: description error --- context.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 4fb3ae6..231bb14 100644 --- a/context.go +++ b/context.go @@ -59,7 +59,7 @@ type Context struct { params *Params skippedNodes *[]skippedNode - // This mutex protect Keys map + // This mutex protects Keys map. mu sync.RWMutex // Keys is a key/value pair exclusively for the context of each request. @@ -71,10 +71,10 @@ type Context struct { // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string - // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() + // queryCache caches the query result from c.Request.URL.Query(). queryCache url.Values - // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH, + // formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH, // or PUT body parameters. formCache url.Values From 8a0f95cc71b9da1a645ad5075c1d2e256d9fa572 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Mon, 20 Dec 2021 17:42:54 +0800 Subject: [PATCH 632/912] fix: typo (#2993) --- binding/binding.go | 2 +- binding/binding_nomsgpack.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index deb7166..0414a34 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -40,7 +40,7 @@ type BindingBody interface { } // BindingUri adds BindUri method to Binding. BindUri is similar with Bind, -// but it read the Params. +// but it reads the Params. type BindingUri interface { Name() string BindUri(map[string][]string, interface{}) error diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index 2342447..f0b667b 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -38,7 +38,7 @@ type BindingBody interface { } // BindingUri adds BindUri method to Binding. BindUri is similar with Bind, -// but it read the Params. +// but it reads the Params. type BindingUri interface { Name() string BindUri(map[string][]string, interface{}) error From 1538ece13fd37d7ecdc541c9b561e48ff14c1615 Mon Sep 17 00:00:00 2001 From: linzi <873804682@qq.com> Date: Thu, 23 Dec 2021 07:51:12 +0800 Subject: [PATCH 633/912] Update README.md (#2997) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f78a64e..6ae567b 100644 --- a/README.md +++ b/README.md @@ -384,7 +384,7 @@ func main() { // 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 + // Single file file, _ := c.FormFile("Filename") log.Println(file.Filename) @@ -417,7 +417,7 @@ func main() { router.POST("/upload", func(c *gin.Context) { // Multipart form form, _ := c.MultipartForm() - files := form.File["upload[]"] + files := form.File["Filename[]"] for _, file := range files { log.Println(file.Filename) From d062a6a6155236883f4c3292379ab94b1eac8b05 Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Sun, 26 Dec 2021 08:02:01 +0800 Subject: [PATCH 634/912] docs: redirect to the correct line of code (#2998) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ae567b..b75df63 100644 --- a/README.md +++ b/README.md @@ -906,7 +906,7 @@ func startPage(c *gin.Context) { var person Person // If `GET`, only `Form` binding engine (`query`) used. // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). - // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88 if c.ShouldBind(&person) == nil { log.Println(person.Name) log.Println(person.Address) From 01363191befaea052a8507ace95efdb70395960a Mon Sep 17 00:00:00 2001 From: jincheng9 Date: Sun, 2 Jan 2022 14:04:07 +0800 Subject: [PATCH 635/912] fix: typo (#3006) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 231bb14..cccb013 100644 --- a/context.go +++ b/context.go @@ -843,7 +843,7 @@ func (c *Context) GetHeader(key string) string { return c.requestHeader(key) } -// GetRawData return stream data. +// GetRawData returns stream data. func (c *Context) GetRawData() ([]byte, error) { return ioutil.ReadAll(c.Request.Body) } From 94153d1e19e20f56f520b31007e667ab6a5caeab Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 2 Jan 2022 19:07:44 +0800 Subject: [PATCH 636/912] test: expose performRequest func (#3012) --- context_test.go | 4 +- gin_test.go | 6 +-- githubapi_test.go | 10 ++-- logger_test.go | 52 ++++++++++----------- middleware_test.go | 17 ++++--- recovery_test.go | 23 +++++---- routergroup_test.go | 4 +- routes_test.go | 111 ++++++++++++++++++++++---------------------- utils_test.go | 8 ++-- 9 files changed, 115 insertions(+), 120 deletions(-) diff --git a/context_test.go b/context_test.go index 4d002c2..9e02aed 100644 --- a/context_test.go +++ b/context_test.go @@ -2036,8 +2036,8 @@ func TestRaceParamsContextCopy(t *testing.T) { }(c.Copy(), c.Param("name")) }) } - performRequest(router, "GET", "/name1/api") - performRequest(router, "GET", "/name2/api") + PerformRequest(router, "GET", "/name1/api") + PerformRequest(router, "GET", "/name2/api") wg.Wait() } diff --git a/gin_test.go b/gin_test.go index 21c43d1..0aece61 100644 --- a/gin_test.go +++ b/gin_test.go @@ -395,7 +395,6 @@ func TestNoMethodWithoutGlobalHandlers(t *testing.T) { } func TestRebuild404Handlers(t *testing.T) { - } func TestNoMethodWithGlobalHandlers(t *testing.T) { @@ -491,7 +490,7 @@ func TestEngineHandleContext(t *testing.T) { } assert.NotPanics(t, func() { - w := performRequest(r, "GET", "/") + w := PerformRequest(r, "GET", "/") assert.Equal(t, 301, w.Code) }) } @@ -524,7 +523,7 @@ func TestEngineHandleContextManyReEntries(t *testing.T) { }) assert.NotPanics(t, func() { - w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value + w := PerformRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value assert.Equal(t, 200, w.Code) assert.Equal(t, expectValue, w.Body.Len()) }) @@ -636,7 +635,6 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { assert.Nil(t, r.trustedCIDRs) assert.Nil(t, err) } - } func parseCIDR(cidr string) *net.IPNet { diff --git a/githubapi_test.go b/githubapi_test.go index 3f440bc..e74bddd 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -302,7 +302,7 @@ func TestShouldBindUri(t *testing.T) { }) path, _ := exampleFromPath("/rest/:name/:id") - w := performRequest(router, http.MethodGet, path) + w := PerformRequest(router, http.MethodGet, path) assert.Equal(t, "ShouldBindUri test OK", w.Body.String()) assert.Equal(t, http.StatusOK, w.Code) } @@ -324,7 +324,7 @@ func TestBindUri(t *testing.T) { }) path, _ := exampleFromPath("/rest/:name/:id") - w := performRequest(router, http.MethodGet, path) + w := PerformRequest(router, http.MethodGet, path) assert.Equal(t, "BindUri test OK", w.Body.String()) assert.Equal(t, http.StatusOK, w.Code) } @@ -342,7 +342,7 @@ func TestBindUriError(t *testing.T) { }) path1, _ := exampleFromPath("/new/rest/:num") - w1 := performRequest(router, http.MethodGet, path1) + w1 := PerformRequest(router, http.MethodGet, path1) assert.Equal(t, http.StatusBadRequest, w1.Code) } @@ -358,7 +358,7 @@ func TestRaceContextCopy(t *testing.T) { go readWriteKeys(c.Copy()) c.String(http.StatusOK, "run OK, no panics") }) - w := performRequest(router, http.MethodGet, "/test/copy/race") + w := PerformRequest(router, http.MethodGet, "/test/copy/race") assert.Equal(t, "run OK, no panics", w.Body.String()) } @@ -389,7 +389,7 @@ func TestGithubAPI(t *testing.T) { for _, route := range githubAPI { path, values := exampleFromPath(route.path) - w := performRequest(router, route.method, path) + w := PerformRequest(router, route.method, path) // TEST assert.Contains(t, w.Body.String(), "\"status\":\"good\"") diff --git a/logger_test.go b/logger_test.go index 80961ce..ec5e6cd 100644 --- a/logger_test.go +++ b/logger_test.go @@ -31,7 +31,7 @@ func TestLogger(t *testing.T) { router.HEAD("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {}) - performRequest(router, "GET", "/example?a=100") + PerformRequest(router, "GET", "/example?a=100") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/example") @@ -41,43 +41,43 @@ func TestLogger(t *testing.T) { // like integration tests because they test the whole logging process rather // than individual functions. Im not sure where these should go. buffer.Reset() - performRequest(router, "POST", "/example") + PerformRequest(router, "POST", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "POST") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "PUT", "/example") + PerformRequest(router, "PUT", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "PUT") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "DELETE", "/example") + PerformRequest(router, "DELETE", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "DELETE") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "PATCH", "/example") + PerformRequest(router, "PATCH", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "PATCH") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "HEAD", "/example") + PerformRequest(router, "HEAD", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "HEAD") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "OPTIONS", "/example") + PerformRequest(router, "OPTIONS", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "OPTIONS") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "GET", "/notfound") + PerformRequest(router, "GET", "/notfound") assert.Contains(t, buffer.String(), "404") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/notfound") @@ -95,7 +95,7 @@ func TestLoggerWithConfig(t *testing.T) { router.HEAD("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {}) - performRequest(router, "GET", "/example?a=100") + PerformRequest(router, "GET", "/example?a=100") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/example") @@ -105,43 +105,43 @@ func TestLoggerWithConfig(t *testing.T) { // like integration tests because they test the whole logging process rather // than individual functions. Im not sure where these should go. buffer.Reset() - performRequest(router, "POST", "/example") + PerformRequest(router, "POST", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "POST") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "PUT", "/example") + PerformRequest(router, "PUT", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "PUT") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "DELETE", "/example") + PerformRequest(router, "DELETE", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "DELETE") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "PATCH", "/example") + PerformRequest(router, "PATCH", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "PATCH") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "HEAD", "/example") + PerformRequest(router, "HEAD", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "HEAD") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "OPTIONS", "/example") + PerformRequest(router, "OPTIONS", "/example") assert.Contains(t, buffer.String(), "200") assert.Contains(t, buffer.String(), "OPTIONS") assert.Contains(t, buffer.String(), "/example") buffer.Reset() - performRequest(router, "GET", "/notfound") + PerformRequest(router, "GET", "/notfound") assert.Contains(t, buffer.String(), "404") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/notfound") @@ -169,7 +169,7 @@ func TestLoggerWithFormatter(t *testing.T) { ) })) router.GET("/example", func(c *Context) {}) - performRequest(router, "GET", "/example?a=100") + PerformRequest(router, "GET", "/example?a=100") // output test assert.Contains(t, buffer.String(), "[FORMATTER TEST]") @@ -209,7 +209,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) { c.Request.Header.Set("X-Forwarded-For", "20.20.20.20") gotKeys = c.Keys }) - performRequest(router, "GET", "/example?a=100") + PerformRequest(router, "GET", "/example?a=100") // output test assert.Contains(t, buffer.String(), "[FORMATTER TEST]") @@ -228,7 +228,6 @@ func TestLoggerWithConfigFormatting(t *testing.T) { assert.Equal(t, "/example?a=100", gotParam.Path) assert.Empty(t, gotParam.ErrorMessage) assert.Equal(t, gotKeys, gotParam.Keys) - } func TestDefaultLogFormatter(t *testing.T) { @@ -282,7 +281,6 @@ func TestDefaultLogFormatter(t *testing.T) { assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam)) assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam)) - } func TestColorForMethod(t *testing.T) { @@ -369,15 +367,15 @@ func TestErrorLogger(t *testing.T) { c.String(http.StatusInternalServerError, "hola!") }) - w := performRequest(router, "GET", "/error") + w := PerformRequest(router, "GET", "/error") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) - w = performRequest(router, "GET", "/abort") + w = PerformRequest(router, "GET", "/abort") assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) - w = performRequest(router, "GET", "/print") + w = PerformRequest(router, "GET", "/print") assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } @@ -389,11 +387,11 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) { router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) - performRequest(router, "GET", "/logged") + PerformRequest(router, "GET", "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() - performRequest(router, "GET", "/skipped") + PerformRequest(router, "GET", "/skipped") assert.Contains(t, buffer.String(), "") } @@ -407,11 +405,11 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) - performRequest(router, "GET", "/logged") + PerformRequest(router, "GET", "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() - performRequest(router, "GET", "/skipped") + PerformRequest(router, "GET", "/skipped") assert.Contains(t, buffer.String(), "") } diff --git a/middleware_test.go b/middleware_test.go index 4b4afd4..e0a756c 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -35,7 +35,7 @@ func TestMiddlewareGeneralCase(t *testing.T) { signature += " XX " }) // RUN - w := performRequest(router, "GET", "/") + w := PerformRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusOK, w.Code) @@ -71,7 +71,7 @@ func TestMiddlewareNoRoute(t *testing.T) { signature += " X " }) // RUN - w := performRequest(router, "GET", "/") + w := PerformRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusNotFound, w.Code) @@ -108,7 +108,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) { signature += " XX " }) // RUN - w := performRequest(router, "GET", "/") + w := PerformRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusMethodNotAllowed, w.Code) @@ -149,7 +149,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) { }) // RUN - w := performRequest(router, "GET", "/") + w := PerformRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusNotFound, w.Code) @@ -175,7 +175,7 @@ func TestMiddlewareAbort(t *testing.T) { }) // RUN - w := performRequest(router, "GET", "/") + w := PerformRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusUnauthorized, w.Code) @@ -190,14 +190,13 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { c.Next() c.AbortWithStatus(http.StatusGone) signature += "B" - }) router.GET("/", func(c *Context) { signature += "C" c.Next() }) // RUN - w := performRequest(router, "GET", "/") + w := PerformRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusGone, w.Code) @@ -220,7 +219,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { signature += "C" }) // RUN - w := performRequest(router, "GET", "/") + w := PerformRequest(router, "GET", "/") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) @@ -247,7 +246,7 @@ func TestMiddlewareWrite(t *testing.T) { }) }) - w := performRequest(router, "GET", "/") + w := PerformRequest(router, "GET", "/") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) diff --git a/recovery_test.go b/recovery_test.go index d164bfa..ac3669a 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -27,7 +27,7 @@ func TestPanicClean(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := performRequest(router, "GET", "/recovery", + w := PerformRequest(router, "GET", "/recovery", header{ Key: "Host", Value: "www.google.com", @@ -57,7 +57,7 @@ func TestPanicInHandler(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := performRequest(router, "GET", "/recovery") + w := PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -68,7 +68,7 @@ func TestPanicInHandler(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = performRequest(router, "GET", "/recovery") + w = PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") @@ -85,7 +85,7 @@ func TestPanicWithAbort(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := performRequest(router, "GET", "/recovery") + w := PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) } @@ -122,7 +122,6 @@ func TestPanicWithBrokenPipe(t *testing.T) { for errno, expectMsg := range expectMsgs { t.Run(expectMsg, func(t *testing.T) { - var buf bytes.Buffer router := New() @@ -137,7 +136,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { panic(e) }) // RUN - w := performRequest(router, "GET", "/recovery") + w := PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, expectCode, w.Code) assert.Contains(t, strings.ToLower(buf.String()), expectMsg) @@ -158,7 +157,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := performRequest(router, "GET", "/recovery") + w := PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -169,7 +168,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = performRequest(router, "GET", "/recovery") + w = PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") @@ -193,7 +192,7 @@ func TestCustomRecovery(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := performRequest(router, "GET", "/recovery") + w := PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -204,7 +203,7 @@ func TestCustomRecovery(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = performRequest(router, "GET", "/recovery") + w = PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") @@ -228,7 +227,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := performRequest(router, "GET", "/recovery") + w := PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -239,7 +238,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = performRequest(router, "GET", "/recovery") + w = PerformRequest(router, "GET", "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") diff --git a/routergroup_test.go b/routergroup_test.go index d6d8b45..232f595 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -80,11 +80,11 @@ func performRequestInGroup(t *testing.T, method string) { panic("unknown method") } - w := performRequest(router, method, "/v1/login/test") + w := PerformRequest(router, method, "/v1/login/test") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 3", w.Body.String()) - w = performRequest(router, method, "/v1/test") + w = PerformRequest(router, method, "/v1/test") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "the method was "+method+" and index 1", w.Body.String()) } diff --git a/routes_test.go b/routes_test.go index ffe3469..b3f0c47 100644 --- a/routes_test.go +++ b/routes_test.go @@ -21,7 +21,8 @@ type header struct { Value string } -func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder { +// PerformRequest for testing gin router. +func PerformRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder { req := httptest.NewRequest(method, path, nil) for _, h := range headers { req.Header.Add(h.Key, h.Value) @@ -42,11 +43,11 @@ func testRouteOK(method string, t *testing.T) { passed = true }) - w := performRequest(r, method, "/test") + w := PerformRequest(r, method, "/test") assert.True(t, passed) assert.Equal(t, http.StatusOK, w.Code) - performRequest(r, method, "/test2") + PerformRequest(r, method, "/test2") assert.True(t, passedAny) } @@ -58,7 +59,7 @@ func testRouteNotOK(method string, t *testing.T) { passed = true }) - w := performRequest(router, method, "/test") + w := PerformRequest(router, method, "/test") assert.False(t, passed) assert.Equal(t, http.StatusNotFound, w.Code) @@ -79,7 +80,7 @@ func testRouteNotOK2(method string, t *testing.T) { passed = true }) - w := performRequest(router, method, "/test") + w := PerformRequest(router, method, "/test") assert.False(t, passed) assert.Equal(t, http.StatusMethodNotAllowed, w.Code) @@ -99,7 +100,7 @@ func TestRouterMethod(t *testing.T) { c.String(http.StatusOK, "sup3") }) - w := performRequest(router, http.MethodPut, "/hey") + w := PerformRequest(router, http.MethodPut, "/hey") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "called", w.Body.String()) @@ -150,50 +151,50 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { router.POST("/path3", func(c *Context) {}) router.PUT("/path4/", func(c *Context) {}) - w := performRequest(router, http.MethodGet, "/path/") + w := PerformRequest(router, http.MethodGet, "/path/") assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) - w = performRequest(router, http.MethodGet, "/path2") + w = PerformRequest(router, http.MethodGet, "/path2") assert.Equal(t, "/path2/", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) - w = performRequest(router, http.MethodPost, "/path3/") + w = PerformRequest(router, http.MethodPost, "/path3/") assert.Equal(t, "/path3", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) - w = performRequest(router, http.MethodPut, "/path4") + w = PerformRequest(router, http.MethodPut, "/path4") assert.Equal(t, "/path4/", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) - w = performRequest(router, http.MethodGet, "/path") + w = PerformRequest(router, http.MethodGet, "/path") assert.Equal(t, http.StatusOK, w.Code) - w = performRequest(router, http.MethodGet, "/path2/") + w = PerformRequest(router, http.MethodGet, "/path2/") assert.Equal(t, http.StatusOK, w.Code) - w = performRequest(router, http.MethodPost, "/path3") + w = PerformRequest(router, http.MethodPost, "/path3") assert.Equal(t, http.StatusOK, w.Code) - w = performRequest(router, http.MethodPut, "/path4/") + w = PerformRequest(router, http.MethodPut, "/path4/") assert.Equal(t, http.StatusOK, w.Code) - w = performRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) assert.Equal(t, "/api/path2/", w.Header().Get("Location")) assert.Equal(t, 301, w.Code) - w = performRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) + w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) assert.Equal(t, 200, w.Code) router.RedirectTrailingSlash = false - w = performRequest(router, http.MethodGet, "/path/") + w = PerformRequest(router, http.MethodGet, "/path/") assert.Equal(t, http.StatusNotFound, w.Code) - w = performRequest(router, http.MethodGet, "/path2") + w = PerformRequest(router, http.MethodGet, "/path2") assert.Equal(t, http.StatusNotFound, w.Code) - w = performRequest(router, http.MethodPost, "/path3/") + w = PerformRequest(router, http.MethodPost, "/path3/") assert.Equal(t, http.StatusNotFound, w.Code) - w = performRequest(router, http.MethodPut, "/path4") + w = PerformRequest(router, http.MethodPut, "/path4") assert.Equal(t, http.StatusNotFound, w.Code) } @@ -207,19 +208,19 @@ func TestRouteRedirectFixedPath(t *testing.T) { router.POST("/PATH3", func(c *Context) {}) router.POST("/Path4/", func(c *Context) {}) - w := performRequest(router, http.MethodGet, "/PATH") + w := PerformRequest(router, http.MethodGet, "/PATH") assert.Equal(t, "/path", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) - w = performRequest(router, http.MethodGet, "/path2") + w = PerformRequest(router, http.MethodGet, "/path2") assert.Equal(t, "/Path2", w.Header().Get("Location")) assert.Equal(t, http.StatusMovedPermanently, w.Code) - w = performRequest(router, http.MethodPost, "/path3") + w = PerformRequest(router, http.MethodPost, "/path3") assert.Equal(t, "/PATH3", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) - w = performRequest(router, http.MethodPost, "/path4") + w = PerformRequest(router, http.MethodPost, "/path4") assert.Equal(t, "/Path4/", w.Header().Get("Location")) assert.Equal(t, http.StatusTemporaryRedirect, w.Code) } @@ -248,7 +249,7 @@ func TestRouteParamsByName(t *testing.T) { assert.False(t, ok) }) - w := performRequest(router, http.MethodGet, "/test/john/smith/is/super/great") + w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "john", name) @@ -281,7 +282,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { assert.False(t, ok) }) - w := performRequest(router, http.MethodGet, "//test//john//smith//is//super//great") + w := PerformRequest(router, http.MethodGet, "//test//john//smith//is//super//great") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "john", name) @@ -309,16 +310,16 @@ func TestRouteStaticFile(t *testing.T) { router.Static("/using_static", dir) router.StaticFile("/result", f.Name()) - w := performRequest(router, http.MethodGet, "/using_static/"+filename) - w2 := performRequest(router, http.MethodGet, "/result") + w := PerformRequest(router, http.MethodGet, "/using_static/"+filename) + w2 := PerformRequest(router, http.MethodGet, "/result") assert.Equal(t, w, w2) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Gin Web Framework", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) - w3 := performRequest(router, http.MethodHead, "/using_static/"+filename) - w4 := performRequest(router, http.MethodHead, "/result") + w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename) + w4 := PerformRequest(router, http.MethodHead, "/result") assert.Equal(t, w3, w4) assert.Equal(t, http.StatusOK, w3.Code) @@ -329,7 +330,7 @@ func TestRouteStaticListingDir(t *testing.T) { router := New() router.StaticFS("/", Dir("./", true)) - w := performRequest(router, http.MethodGet, "/") + w := PerformRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "gin.go") @@ -341,7 +342,7 @@ func TestRouteStaticNoListing(t *testing.T) { router := New() router.Static("/", "./") - w := performRequest(router, http.MethodGet, "/") + w := PerformRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusNotFound, w.Code) assert.NotContains(t, w.Body.String(), "gin.go") @@ -356,7 +357,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { }) static.Static("/", "./") - w := performRequest(router, http.MethodGet, "/gin.go") + w := PerformRequest(router, http.MethodGet, "/gin.go") assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") @@ -372,13 +373,13 @@ func TestRouteNotAllowedEnabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = true router.POST("/path", func(c *Context) {}) - w := performRequest(router, http.MethodGet, "/path") + w := PerformRequest(router, http.MethodGet, "/path") assert.Equal(t, http.StatusMethodNotAllowed, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) - w = performRequest(router, http.MethodGet, "/path") + w = PerformRequest(router, http.MethodGet, "/path") assert.Equal(t, "responseText", w.Body.String()) assert.Equal(t, http.StatusTeapot, w.Code) } @@ -389,7 +390,7 @@ func TestRouteNotAllowedEnabled2(t *testing.T) { // add one methodTree to trees router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}}) router.GET("/path2", func(c *Context) {}) - w := performRequest(router, http.MethodPost, "/path2") + w := PerformRequest(router, http.MethodPost, "/path2") assert.Equal(t, http.StatusMethodNotAllowed, w.Code) } @@ -397,13 +398,13 @@ func TestRouteNotAllowedDisabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = false router.POST("/path", func(c *Context) {}) - w := performRequest(router, http.MethodGet, "/path") + w := PerformRequest(router, http.MethodGet, "/path") assert.Equal(t, http.StatusNotFound, w.Code) router.NoMethod(func(c *Context) { c.String(http.StatusTeapot, "responseText") }) - w = performRequest(router, http.MethodGet, "/path") + w = PerformRequest(router, http.MethodGet, "/path") assert.Equal(t, "404 page not found", w.Body.String()) assert.Equal(t, http.StatusNotFound, w.Code) } @@ -423,7 +424,7 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) { {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { - w := performRequest(router, "GET", tr.route) + w := PerformRequest(router, "GET", tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) @@ -453,7 +454,7 @@ func TestRouterNotFound(t *testing.T) { {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { - w := performRequest(router, http.MethodGet, tr.route) + w := PerformRequest(router, http.MethodGet, tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) @@ -466,20 +467,20 @@ func TestRouterNotFound(t *testing.T) { c.AbortWithStatus(http.StatusNotFound) notFound = true }) - w := performRequest(router, http.MethodGet, "/nope") + w := PerformRequest(router, http.MethodGet, "/nope") assert.Equal(t, http.StatusNotFound, w.Code) assert.True(t, notFound) // Test other method than GET (want 307 instead of 301) router.PATCH("/path", func(c *Context) {}) - w = performRequest(router, http.MethodPatch, "/path/") + w = PerformRequest(router, http.MethodPatch, "/path/") assert.Equal(t, http.StatusTemporaryRedirect, w.Code) assert.Equal(t, "map[Location:[/path]]", fmt.Sprint(w.Header())) // Test special case where no node for the prefix "/" exists router = New() router.GET("/a", func(c *Context) {}) - w = performRequest(router, http.MethodGet, "/") + w = PerformRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusNotFound, w.Code) // Reproduction test for the bug of issue #2843 @@ -492,9 +493,9 @@ func TestRouterNotFound(t *testing.T) { router.GET("/logout", func(c *Context) { c.String(200, "logout") }) - w = performRequest(router, http.MethodGet, "/login") + w = PerformRequest(router, http.MethodGet, "/login") assert.Equal(t, "login", w.Body.String()) - w = performRequest(router, http.MethodGet, "/logout") + w = PerformRequest(router, http.MethodGet, "/logout") assert.Equal(t, "logout", w.Body.String()) } @@ -505,10 +506,10 @@ func TestRouterStaticFSNotFound(t *testing.T) { c.String(404, "non existent") }) - w := performRequest(router, http.MethodGet, "/nonexistent") + w := PerformRequest(router, http.MethodGet, "/nonexistent") assert.Equal(t, "non existent", w.Body.String()) - w = performRequest(router, http.MethodHead, "/nonexistent") + w = PerformRequest(router, http.MethodHead, "/nonexistent") assert.Equal(t, "non existent", w.Body.String()) } @@ -518,7 +519,7 @@ func TestRouterStaticFSFileNotFound(t *testing.T) { router.StaticFS("/", http.FileSystem(http.Dir("."))) assert.NotPanics(t, func() { - performRequest(router, http.MethodGet, "/nonexistent") + PerformRequest(router, http.MethodGet, "/nonexistent") }) } @@ -535,11 +536,11 @@ func TestMiddlewareCalledOnceByRouterStaticFSNotFound(t *testing.T) { router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) // First access - performRequest(router, http.MethodGet, "/nonexistent") + PerformRequest(router, http.MethodGet, "/nonexistent") assert.Equal(t, 1, middlewareCalledNum) // Second access - performRequest(router, http.MethodHead, "/nonexistent") + PerformRequest(router, http.MethodHead, "/nonexistent") assert.Equal(t, 2, middlewareCalledNum) } @@ -558,7 +559,7 @@ func TestRouteRawPath(t *testing.T) { assert.Equal(t, "222", num) }) - w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222") + w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/222") assert.Equal(t, http.StatusOK, w.Code) } @@ -578,7 +579,7 @@ func TestRouteRawPathNoUnescape(t *testing.T) { assert.Equal(t, "333", num) }) - w := performRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333") + w := PerformRequest(route, http.MethodPost, "/project/Some%2FOther%2FProject/build/333") assert.Equal(t, http.StatusOK, w.Code) } @@ -589,7 +590,7 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) { c.Next() }) - w := performRequest(route, http.MethodGet, "/NotFound") + w := PerformRequest(route, http.MethodGet, "/NotFound") assert.Equal(t, 421, w.Code) assert.Equal(t, 0, w.Body.Len()) } @@ -623,7 +624,7 @@ func TestRouteContextHoldsFullPath(t *testing.T) { } for _, route := range routes { - w := performRequest(router, http.MethodGet, route) + w := PerformRequest(router, http.MethodGet, route) assert.Equal(t, http.StatusOK, w.Code) } @@ -633,6 +634,6 @@ func TestRouteContextHoldsFullPath(t *testing.T) { assert.Equal(t, "", c.FullPath()) }) - w := performRequest(router, http.MethodGet, "/not-found") + w := PerformRequest(router, http.MethodGet, "/not-found") assert.Equal(t, http.StatusNotFound, w.Code) } diff --git a/utils_test.go b/utils_test.go index cc486c3..b50914f 100644 --- a/utils_test.go +++ b/utils_test.go @@ -45,11 +45,11 @@ func TestWrap(t *testing.T) { fmt.Fprint(w, "hola!") })) - w := performRequest(router, "POST", "/path") + w := PerformRequest(router, "POST", "/path") assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hello", w.Body.String()) - w = performRequest(router, "GET", "/path2") + w = PerformRequest(router, "GET", "/path2") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "hola!", w.Body.String()) } @@ -119,13 +119,13 @@ func TestBindMiddleware(t *testing.T) { called = true value = c.MustGet(BindKey).(*bindTestStruct) }) - performRequest(router, "GET", "/?foo=hola&bar=10") + PerformRequest(router, "GET", "/?foo=hola&bar=10") assert.True(t, called) assert.Equal(t, "hola", value.Foo) assert.Equal(t, 10, value.Bar) called = false - performRequest(router, "GET", "/?foo=hola&bar=1") + PerformRequest(router, "GET", "/?foo=hola&bar=1") assert.False(t, called) assert.Panics(t, func() { From 6868ed18cc00f4ee500710462b5551a5e8c116c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 8 Jan 2022 17:25:31 +0800 Subject: [PATCH 637/912] Bump github.com/go-playground/validator/v10 from 10.9.0 to 10.10.0 (#3013) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 05b9760..118eb76 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.9.0 + github.com/go-playground/validator/v10 v10.10.0 github.com/goccy/go-json v0.8.1 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 diff --git a/go.sum b/go.sum index adacf5e..c16d8c8 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= -github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= From 336ce0d47532e89657b711af52bf5a8e01128755 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jan 2022 08:15:12 +0800 Subject: [PATCH 638/912] Bump github.com/goccy/go-json from 0.8.1 to 0.9.0 (#3021) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.8.1 to 0.9.0. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.8.1...v0.9.0) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 118eb76..3548835 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 - github.com/goccy/go-json v0.8.1 + github.com/goccy/go-json v0.9.0 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index c16d8c8..59efbf7 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= -github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.0 h1:2flW7bkbrRgU8VuDi0WXDqTmPimjv1thfxkPe8sug+8= +github.com/goccy/go-json v0.9.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From 1b28e2b0303b6e5ecdea70890ba1ee8c5950892b Mon Sep 17 00:00:00 2001 From: jarodsong6 Date: Wed, 12 Jan 2022 23:12:32 +0900 Subject: [PATCH 639/912] Fix typo (#3023) Co-authored-by: lin.song --- ginS/gins.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ginS/gins.go b/ginS/gins.go index ed054bf..0802e08 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -118,7 +118,7 @@ func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes { return engine().StaticFS(relativePath, fs) } -// Use attaches a global middleware to the router. i.e. the middlewares attached though Use() will be +// Use attaches a global middleware to the router. i.e. the middlewares attached through Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { From 580e7da6eed01e2926de1240ec31f6473cd1a2af Mon Sep 17 00:00:00 2001 From: Waynerv Date: Thu, 20 Jan 2022 22:33:35 +0800 Subject: [PATCH 640/912] Remove incorrect comments about context.Bind() and improve docs (#3028) --- context.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/context.go b/context.go index cccb013..d69df70 100644 --- a/context.go +++ b/context.go @@ -601,11 +601,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error return err } -// Bind checks the Content-Type to select a binding engine automatically, -// Depending on the "Content-Type" header different bindings are used: +// Bind checks the Method and Content-Type to select a binding engine automatically, +// Depending on the "Content-Type" header different bindings are used, for example: // "application/json" --> JSON binding // "application/xml" --> XML binding -// otherwise --> returns an error. // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. // It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. @@ -660,14 +659,13 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { return nil } -// ShouldBind checks the Content-Type to select a binding engine automatically, -// Depending on the "Content-Type" header different bindings are used: +// ShouldBind checks the Method and Content-Type to select a binding engine automatically, +// Depending on the "Content-Type" header different bindings are used, for example: // "application/json" --> JSON binding // "application/xml" --> XML binding -// otherwise --> returns an error // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. -// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid. +// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid. func (c *Context) ShouldBind(obj interface{}) error { b := binding.Default(c.Request.Method, c.ContentType()) return c.ShouldBindWith(obj, b) From 41d38fb68cbd4460994d9a6b30229ce1009db01d Mon Sep 17 00:00:00 2001 From: MichaelDeSteven <51652084+MichaelDeSteven@users.noreply.github.com> Date: Sat, 5 Feb 2022 09:30:38 +0800 Subject: [PATCH 641/912] fix typo (#3044) --- tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index a30d349..18e50ee 100644 --- a/tree.go +++ b/tree.go @@ -81,7 +81,7 @@ func longestCommonPrefix(a, b string) int { return i } -// addChild will add a child node, keeping wildcards at the end +// addChild will add a child node, keeping wildcardChild at the end func (n *node) addChild(child *node) { if n.wildChild && len(n.children) > 0 { wildcardChild := n.children[len(n.children)-1] @@ -296,7 +296,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) break } - // The wildcard name must not contain ':' and '*' + // The wildcard name must only contain one ':' or '*' character if !valid { panic("only one wildcard per path segment is allowed, has: '" + wildcard + "' in path '" + fullPath + "'") From b94075ff1dab162c8df7f0d1ee3f6a854eb5c4d1 Mon Sep 17 00:00:00 2001 From: Sasha Melentyev Date: Sat, 5 Feb 2022 04:36:38 +0300 Subject: [PATCH 642/912] ci: bump golangci-lint version (#3037) --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index c25a909..f3927b3 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -21,7 +21,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.43.0 + version: v1.44.0 args: --verbose test: needs: lint From c19374c4711a5587e02b40d25656e3551d3bded8 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 5 Feb 2022 20:57:13 +0800 Subject: [PATCH 643/912] feat(CodeQL): Discover vulnerabilities across a codebase with CodeQL (#3049) * feat(CodeQL): Discover vulnerabilities across a codebase with CodeQL Signed-off-by: Bo-Yi Wu * fix: unknown directive: retract Signed-off-by: Bo-Yi Wu --- .github/workflows/codeql.yml | 49 ++++++++++++++++++++++++++++++++++++ go.mod | 2 -- 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..4a081e0 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,49 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '0 17 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + permissions: + # required for all workflows + security-events: write + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + # TODO: Enable for javascript later + language: [ 'go'] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/go.mod b/go.mod index 3548835..b088f26 100644 --- a/go.mod +++ b/go.mod @@ -13,5 +13,3 @@ require ( google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 ) - -retract v1.7.5 From b40ded18375ad1fd955bf32ee74227c1e5294c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B6=9B=E5=8F=94?= Date: Sat, 5 Feb 2022 21:13:20 +0800 Subject: [PATCH 644/912] Add h2c support (#1398) --- gin.go | 22 ++++++++++++++++++---- gin_test.go | 34 ++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 1 + 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/gin.go b/gin.go index 9bd4c46..b54dbd8 100644 --- a/gin.go +++ b/gin.go @@ -16,6 +16,8 @@ import ( "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/render" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" ) const defaultMultipartMemory = 32 << 20 // 32 MB @@ -141,6 +143,9 @@ type Engine struct { // method call. MaxMultipartMemory int64 + // Enable h2c support. + UseH2C bool + delims render.Delims secureJSONPrefix string HTMLRender render.HTMLRender @@ -207,6 +212,15 @@ func Default() *Engine { return engine } +func (engine *Engine) Handler() http.Handler { + if !engine.UseH2C { + return engine + } + + h2s := &http2.Server{} + return h2c.NewHandler(engine, h2s) +} + func (engine *Engine) allocateContext() *Context { v := make(Params, 0, engine.maxParams) skippedNodes := make([]skippedNode, 0, engine.maxSections) @@ -361,7 +375,7 @@ func (engine *Engine) Run(addr ...string) (err error) { address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) - err = http.ListenAndServe(address, engine) + err = http.ListenAndServe(address, engine.Handler()) return } @@ -480,7 +494,7 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") } - err = http.ListenAndServeTLS(addr, certFile, keyFile, engine) + err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler()) return } @@ -503,7 +517,7 @@ func (engine *Engine) RunUnix(file string) (err error) { defer listener.Close() defer os.Remove(file) - err = http.Serve(listener, engine) + err = http.Serve(listener, engine.Handler()) return } @@ -540,7 +554,7 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) { "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") } - err = http.Serve(listener, engine) + err = http.Serve(listener, engine.Handler()) return } diff --git a/gin_test.go b/gin_test.go index 0aece61..629a109 100644 --- a/gin_test.go +++ b/gin_test.go @@ -19,6 +19,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "golang.org/x/net/http2" ) func formatAsDate(t time.Time) string { @@ -79,6 +80,39 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { assert.Equal(t, "

Hello world

", string(resp)) } +func TestH2c(t *testing.T) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + fmt.Println(err) + } + r := Default() + r.UseH2C = true + r.GET("/", func(c *Context) { + c.String(200, "

Hello world

") + }) + go http.Serve(ln, r.Handler()) + defer ln.Close() + + url := "http://" + ln.Addr().String() + "/" + + http := http.Client{ + Transport: &http2.Transport{ + AllowHTTP: true, + DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { + return net.Dial(netw, addr) + }, + }, + } + + res, err := http.Get(url) + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "

Hello world

", string(resp)) +} + func TestLoadHTMLGlobTestMode(t *testing.T) { ts := setupHTMLFiles( t, diff --git a/go.mod b/go.mod index b088f26..b59e590 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 github.com/ugorji/go/codec v1.2.6 + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 59efbf7..c01ba52 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,7 @@ github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 87e40d6b150f62f26ccf212ae785d24baeb4f6cd Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 7 Feb 2022 23:15:44 +0800 Subject: [PATCH 645/912] feat: fix lint error (#3050) --- gin_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gin_test.go b/gin_test.go index 629a109..ae1762e 100644 --- a/gin_test.go +++ b/gin_test.go @@ -90,7 +90,12 @@ func TestH2c(t *testing.T) { r.GET("/", func(c *Context) { c.String(200, "

Hello world

") }) - go http.Serve(ln, r.Handler()) + go func() { + err := http.Serve(ln, r.Handler()) + if err != nil { + fmt.Println(err) + } + }() defer ln.Close() url := "http://" + ln.Addr().String() + "/" From 375714258462e46df07337b31dae4370d03ab28d Mon Sep 17 00:00:00 2001 From: bestgopher <84328409@qq.com> Date: Mon, 14 Feb 2022 14:39:57 +0800 Subject: [PATCH 646/912] Update routergroup.go (#3056) --- routergroup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 27d7aad..b84fcca 100644 --- a/routergroup.go +++ b/routergroup.go @@ -12,7 +12,7 @@ import ( ) var ( - // reg match english letters for http method name + // regEnLetter matches english letters for http method name regEnLetter = regexp.MustCompile("^[A-Z]+$") // anyMethods for RouterGroup Any method From 5f0b6cdfc4313b0d43e15e03ab9c6d6e2e66af03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 12 Mar 2022 20:42:58 +0800 Subject: [PATCH 647/912] Bump github.com/goccy/go-json from 0.9.0 to 0.9.5 (#3069) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b59e590..8087227 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.13 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 - github.com/goccy/go-json v0.9.0 + github.com/goccy/go-json v0.9.5 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index c01ba52..7f89245 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.9.0 h1:2flW7bkbrRgU8VuDi0WXDqTmPimjv1thfxkPe8sug+8= -github.com/goccy/go-json v0.9.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.5 h1:ooSMW526ZjK+EaL5elrSyN2EzIfi/3V0m4+HJEDYLik= +github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From ecadd825920ab42d188ba4cd6bf36360b656ef55 Mon Sep 17 00:00:00 2001 From: phithon Date: Tue, 15 Mar 2022 11:24:17 +0800 Subject: [PATCH 648/912] doc: change the form name of upload example code (#3076) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b75df63..d3fa075 100644 --- a/README.md +++ b/README.md @@ -385,7 +385,7 @@ func main() { router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // Single file - file, _ := c.FormFile("Filename") + file, _ := c.FormFile("file") log.Println(file.Filename) // Upload the file to specific dst. @@ -417,7 +417,7 @@ func main() { router.POST("/upload", func(c *gin.Context) { // Multipart form form, _ := c.MultipartForm() - files := form.File["Filename[]"] + files := form.File["upload[]"] for _, file := range files { log.Println(file.Filename) From 7927a45143da95dc4151519ceda2de78a7cd55c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Mar 2022 23:19:06 +0800 Subject: [PATCH 649/912] Bump actions/checkout from 2 to 3 (#3068) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- .github/workflows/gin.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4a081e0..4cbc455 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index f3927b3..5b818a9 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -17,7 +17,7 @@ jobs: with: go-version: '^1.16' - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup golangci-lint uses: golangci/golangci-lint-action@v2 with: @@ -48,7 +48,7 @@ jobs: go-version: ${{ matrix.go }} - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: ${{ github.ref }} From 90330e2a76a76686dc62d18ba55a4f63768c589b Mon Sep 17 00:00:00 2001 From: linzi <873804682@qq.com> Date: Thu, 17 Mar 2022 11:55:08 +0800 Subject: [PATCH 650/912] Distinguish between group and nested group (#3083) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d3fa075..2d11353 100644 --- a/README.md +++ b/README.md @@ -513,6 +513,7 @@ func main() { // nested group testing := authorized.Group("testing") + // visit 0.0.0.0:8080/testing/analytics testing.GET("/analytics", analyticsEndpoint) } From 8860527de602b561500d68cb345c8b07de22805e Mon Sep 17 00:00:00 2001 From: metal A-wing Date: Thu, 17 Mar 2022 03:56:16 +0000 Subject: [PATCH 651/912] feat attachment filename support utf8 (#3071) --- context.go | 7 +++++-- context_test.go | 14 ++++++++++++++ utils.go | 11 +++++++++++ utils_test.go | 5 +++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index d69df70..48d7550 100644 --- a/context.go +++ b/context.go @@ -6,7 +6,6 @@ package gin import ( "errors" - "fmt" "io" "io/ioutil" "log" @@ -1018,7 +1017,11 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) { // FileAttachment writes the specified file into the body stream in an efficient way // On the client side, the file will typically be downloaded with the given filename func (c *Context) FileAttachment(filepath, filename string) { - c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + if isASCII(filename) { + c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`) + } else { + c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename)) + } http.ServeFile(c.Writer, c.Request, filepath) } diff --git a/context_test.go b/context_test.go index 9e02aed..4eed164 100644 --- a/context_test.go +++ b/context_test.go @@ -15,6 +15,7 @@ import ( "net" "net/http" "net/http/httptest" + "net/url" "os" "reflect" "strings" @@ -1033,6 +1034,19 @@ func TestContextRenderAttachment(t *testing.T) { assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition")) } +func TestContextRenderUTF8Attachment(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + newFilename := "new🧡_filename.go" + + c.Request, _ = http.NewRequest("GET", "/", nil) + c.FileAttachment("./gin.go", newFilename) + + assert.Equal(t, 200, w.Code) + assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get("Content-Disposition")) +} + // TestContextRenderYAML tests that the response is serialized as YAML // and Content-Type is set to application/x-yaml func TestContextRenderYAML(t *testing.T) { diff --git a/utils.go b/utils.go index c32f0ee..e4599ea 100644 --- a/utils.go +++ b/utils.go @@ -12,6 +12,7 @@ import ( "reflect" "runtime" "strings" + "unicode" ) // BindKey indicates a default bind key. @@ -151,3 +152,13 @@ func resolveAddress(addr []string) string { panic("too many parameters") } } + +// https://stackoverflow.com/questions/53069040/checking-a-string-contains-only-ascii-characters +func isASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] > unicode.MaxASCII { + return false + } + } + return true +} diff --git a/utils_test.go b/utils_test.go index b50914f..d2a740b 100644 --- a/utils_test.go +++ b/utils_test.go @@ -143,3 +143,8 @@ func TestMarshalXMLforH(t *testing.T) { e := h.MarshalXML(enc, x) assert.Error(t, e) } + +func TestIsASCII(t *testing.T) { + assert.Equal(t, isASCII("test"), true) + assert.Equal(t, isASCII("🧡💛💚💙💜"), false) +} From 417b142703594c1a7dff030e67c38e1dfec9f1fc Mon Sep 17 00:00:00 2001 From: thinkgo <49174849+thinkgos@users.noreply.github.com> Date: Fri, 18 Mar 2022 09:52:23 +0800 Subject: [PATCH 652/912] feat: add StaticFileFS (#2749) * RouterGroup.StaticFileFS added add StaticFileFS ad README * fix Static content mistake * update README `tab` improve StaticFile and StaticFileFS code, use staticFileHandler --- README.md | 3 ++- routergroup.go | 19 ++++++++++++++++--- routergroup_test.go | 12 ++++++++++++ routes_test.go | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2d11353..4b4d236 100644 --- a/README.md +++ b/README.md @@ -1244,7 +1244,8 @@ func main() { router.Static("/assets", "./assets") router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFile("/favicon.ico", "./resources/favicon.ico") - + router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system")) + // Listen and serve on 0.0.0.0:8080 router.Run(":8080") } diff --git a/routergroup.go b/routergroup.go index b84fcca..3fba3a9 100644 --- a/routergroup.go +++ b/routergroup.go @@ -44,6 +44,7 @@ type IRoutes interface { HEAD(string, ...HandlerFunc) IRoutes StaticFile(string, string) IRoutes + StaticFileFS(string, string, http.FileSystem) IRoutes Static(string, string) IRoutes StaticFS(string, http.FileSystem) IRoutes } @@ -153,12 +154,24 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou // StaticFile registers a single route in order to serve a single file of the local filesystem. // router.StaticFile("favicon.ico", "./resources/favicon.ico") func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { + return group.staticFileHandler(relativePath, func(c *Context) { + c.File(filepath) + }) +} + +// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead.. +// router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false}) +// Gin by default user: gin.Dir() +func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes { + return group.staticFileHandler(relativePath, func(c *Context) { + c.FileFromFS(filepath, fs) + }) +} + +func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { panic("URL parameters can not be used when serving a static file") } - handler := func(c *Context) { - c.File(filepath) - } group.GET(relativePath, handler) group.HEAD(relativePath, handler) return group.returnObj() diff --git a/routergroup_test.go b/routergroup_test.go index 232f595..c1fad3a 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -111,6 +111,17 @@ func TestRouterGroupInvalidStaticFile(t *testing.T) { }) } +func TestRouterGroupInvalidStaticFileFS(t *testing.T) { + router := New() + assert.Panics(t, func() { + router.StaticFileFS("/path/:param", "favicon.ico", Dir(".", false)) + }) + + assert.Panics(t, func() { + router.StaticFileFS("/path/*param", "favicon.ico", Dir(".", false)) + }) +} + func TestRouterGroupTooManyHandlers(t *testing.T) { const ( panicValue = "too many handlers" @@ -177,6 +188,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) { assert.Equal(t, r, r.HEAD("/", handler)) assert.Equal(t, r, r.StaticFile("/file", ".")) + assert.Equal(t, r, r.StaticFileFS("/static2", ".", Dir(".", false))) assert.Equal(t, r, r.Static("/static", ".")) assert.Equal(t, r, r.StaticFS("/static2", Dir(".", false))) } diff --git a/routes_test.go b/routes_test.go index b3f0c47..4a0cb49 100644 --- a/routes_test.go +++ b/routes_test.go @@ -325,6 +325,40 @@ func TestRouteStaticFile(t *testing.T) { assert.Equal(t, http.StatusOK, w3.Code) } +// TestHandleStaticFile - ensure the static file handles properly +func TestRouteStaticFileFS(t *testing.T) { + // SETUP file + testRoot, _ := os.Getwd() + f, err := ioutil.TempFile(testRoot, "") + if err != nil { + t.Error(err) + } + defer os.Remove(f.Name()) + _, err = f.WriteString("Gin Web Framework") + assert.NoError(t, err) + f.Close() + + dir, filename := filepath.Split(f.Name()) + // SETUP gin + router := New() + router.Static("/using_static", dir) + router.StaticFileFS("/result_fs", filename, Dir(dir, false)) + + w := performRequest(router, http.MethodGet, "/using_static/"+filename) + w2 := performRequest(router, http.MethodGet, "/result_fs") + + assert.Equal(t, w, w2) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "Gin Web Framework", w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + + w3 := performRequest(router, http.MethodHead, "/using_static/"+filename) + w4 := performRequest(router, http.MethodHead, "/result_fs") + + assert.Equal(t, w3, w4) + assert.Equal(t, http.StatusOK, w3.Code) +} + // TestHandleStaticDir - ensure the root/sub dir handles properly func TestRouteStaticListingDir(t *testing.T) { router := New() From fcd36c549da01a72aef0fc6a176a27badb9e3fe9 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Fri, 18 Mar 2022 11:55:25 +0800 Subject: [PATCH 653/912] fix: test error (#3087) --- routes_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/routes_test.go b/routes_test.go index 4a0cb49..5643097 100644 --- a/routes_test.go +++ b/routes_test.go @@ -344,16 +344,16 @@ func TestRouteStaticFileFS(t *testing.T) { router.Static("/using_static", dir) router.StaticFileFS("/result_fs", filename, Dir(dir, false)) - w := performRequest(router, http.MethodGet, "/using_static/"+filename) - w2 := performRequest(router, http.MethodGet, "/result_fs") + w := PerformRequest(router, http.MethodGet, "/using_static/"+filename) + w2 := PerformRequest(router, http.MethodGet, "/result_fs") assert.Equal(t, w, w2) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "Gin Web Framework", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) - w3 := performRequest(router, http.MethodHead, "/using_static/"+filename) - w4 := performRequest(router, http.MethodHead, "/result_fs") + w3 := PerformRequest(router, http.MethodHead, "/using_static/"+filename) + w4 := PerformRequest(router, http.MethodHead, "/result_fs") assert.Equal(t, w3, w4) assert.Equal(t, http.StatusOK, w3.Code) From f073e33fb93288aecd8bfda789f12c03e5bda829 Mon Sep 17 00:00:00 2001 From: a2tt Date: Fri, 18 Mar 2022 18:41:09 +0900 Subject: [PATCH 654/912] fix: typo (#3086) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b4d236..9a9785e 100644 --- a/README.md +++ b/README.md @@ -1411,7 +1411,7 @@ import ( func formatAsDate(t time.Time) string { year, month, day := t.Date() - return fmt.Sprintf("%d%02d/%02d", year, month, day) + return fmt.Sprintf("%d/%02d/%02d", year, month, day) } func main() { From d8dfaaeb2e3b6c1c6a5ac5ffe41d2e20be8f7958 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Mar 2022 21:19:57 +0800 Subject: [PATCH 655/912] Bump golangci/golangci-lint-action from 2 to 3.1.0 (#3063) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 5b818a9..1aecda5 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -19,7 +19,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3.1.0 with: version: v1.44.0 args: --verbose From 1e24473f5f6b80aff859826edc29f8ae87cca2ff Mon Sep 17 00:00:00 2001 From: Mike <38686456+icy4ever@users.noreply.github.com> Date: Sun, 20 Mar 2022 21:26:12 +0800 Subject: [PATCH 656/912] Annotation fix (#3088) * fix annotation * fix annotation --- tree.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 18e50ee..88100ee 100644 --- a/tree.go +++ b/tree.go @@ -325,7 +325,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) n.priority++ // if the path doesn't end with the wildcard, then there - // will be another non-wildcard subpath starting with '/' + // will be another subpath starting with '/' if len(wildcard) < len(path) { path = path[len(wildcard):] From 9701b651b7cf2a0e1d501674f6cc92d8b8ee81bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Mar 2022 21:31:42 +0800 Subject: [PATCH 657/912] Bump github.com/ugorji/go/codec from 1.2.6 to 1.2.7 (#3064) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.2.6 to 1.2.7. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/v1.2.6...v1.2.7) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 8087227..7a9249c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 - github.com/ugorji/go/codec v1.2.6 + github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 7f89245..6006ffb 100644 --- a/go.sum +++ b/go.sum @@ -47,10 +47,10 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= -github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= -github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= -github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= From 2bde107686759098e2d64273bc79d1a0216a4500 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 21 Mar 2022 09:43:17 +0800 Subject: [PATCH 658/912] test support go1.18 (#2990) --- any.go | 10 +++ binding/any.go | 10 +++ binding/binding.go | 12 ++-- binding/binding_nomsgpack.go | 12 ++-- binding/binding_test.go | 18 +++--- binding/default_validator.go | 6 +- binding/default_validator_test.go | 2 +- binding/form.go | 6 +- binding/form_mapping.go | 14 ++-- binding/form_mapping_test.go | 4 +- binding/header.go | 4 +- binding/json.go | 6 +- binding/msgpack.go | 6 +- binding/msgpack_test.go | 2 +- binding/multipart_form_mapping_test.go | 2 +- binding/protobuf.go | 4 +- binding/query.go | 2 +- binding/uri.go | 2 +- binding/validate_test.go | 4 +- binding/xml.go | 6 +- binding/yaml.go | 6 +- context.go | 88 +++++++++++++------------- context_test.go | 16 ++--- debug.go | 2 +- deprecated.go | 2 +- errors.go | 10 +-- errors_test.go | 2 +- gin.go | 2 +- gin_test.go | 4 +- logger.go | 2 +- logger_test.go | 2 +- recovery.go | 4 +- recovery_test.go | 6 +- render/any.go | 10 +++ render/html.go | 8 +-- render/json.go | 14 ++-- render/msgpack.go | 4 +- render/protobuf.go | 2 +- render/render_msgpack_test.go | 2 +- render/render_test.go | 36 +++++------ render/text.go | 4 +- render/xml.go | 2 +- render/yaml.go | 2 +- testdata/protoexample/any.go | 10 +++ testdata/protoexample/test.pb.go | 6 +- tree_test.go | 2 +- utils.go | 8 +-- 47 files changed, 214 insertions(+), 174 deletions(-) create mode 100644 any.go create mode 100644 binding/any.go create mode 100644 render/any.go create mode 100644 testdata/protoexample/any.go diff --git a/any.go b/any.go new file mode 100644 index 0000000..a0104dc --- /dev/null +++ b/any.go @@ -0,0 +1,10 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build !go1.18 +// +build !go1.18 + +package gin + +type any = interface{} diff --git a/binding/any.go b/binding/any.go new file mode 100644 index 0000000..1331d39 --- /dev/null +++ b/binding/any.go @@ -0,0 +1,10 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build !go1.18 +// +build !go1.18 + +package binding + +type any = interface{} diff --git a/binding/binding.go b/binding/binding.go index 0414a34..703a1cf 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -29,21 +29,21 @@ const ( // the form POST. type Binding interface { Name() string - Bind(*http.Request, interface{}) error + Bind(*http.Request, any) 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 + BindBody([]byte, any) error } // BindingUri adds BindUri method to Binding. BindUri is similar with Bind, // but it reads the Params. type BindingUri interface { Name() string - BindUri(map[string][]string, interface{}) error + BindUri(map[string][]string, any) error } // StructValidator is the minimal interface which needs to be implemented in @@ -57,11 +57,11 @@ type StructValidator interface { // If the received type is a struct or pointer to a struct, the validation should be performed. // If the struct is not valid or the validation itself fails, a descriptive error should be returned. // Otherwise nil must be returned. - ValidateStruct(interface{}) error + ValidateStruct(any) error // Engine returns the underlying validator engine which powers the // StructValidator implementation. - Engine() interface{} + Engine() any } // Validator is the default validator which implements the StructValidator @@ -110,7 +110,7 @@ func Default(method, contentType string) Binding { } } -func validate(obj interface{}) error { +func validate(obj any) error { if Validator == nil { return nil } diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index f0b667b..b381854 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -27,21 +27,21 @@ const ( // the form POST. type Binding interface { Name() string - Bind(*http.Request, interface{}) error + Bind(*http.Request, any) 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 + BindBody([]byte, any) error } // BindingUri adds BindUri method to Binding. BindUri is similar with Bind, // but it reads the Params. type BindingUri interface { Name() string - BindUri(map[string][]string, interface{}) error + BindUri(map[string][]string, any) error } // StructValidator is the minimal interface which needs to be implemented in @@ -54,11 +54,11 @@ type StructValidator interface { // If the received type is a struct or pointer to a struct, the validation should be performed. // If the struct is not valid or the validation itself fails, a descriptive error should be returned. // Otherwise nil must be returned. - ValidateStruct(interface{}) error + ValidateStruct(any) error // Engine returns the underlying validator engine which powers the // StructValidator implementation. - Engine() interface{} + Engine() any } // Validator is the default validator which implements the StructValidator @@ -104,7 +104,7 @@ func Default(method, contentType string) Binding { } } -func validate(obj interface{}) error { +func validate(obj any) error { if Validator == nil { return nil } diff --git a/binding/binding_test.go b/binding/binding_test.go index 5b0ce39..b1edbf5 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -61,11 +61,11 @@ type FooDefaultBarStruct struct { } type FooStructUseNumber struct { - Foo interface{} `json:"foo" binding:"required"` + Foo any `json:"foo" binding:"required"` } type FooStructDisallowUnknownFields struct { - Foo interface{} `json:"foo" binding:"required"` + Foo any `json:"foo" binding:"required"` } type FooBarStructForTimeType struct { @@ -93,7 +93,7 @@ type FooStructForTimeTypeFailLocation struct { } type FooStructForMapType struct { - MapFoo map[string]interface{} `form:"map_foo"` + MapFoo map[string]any `form:"map_foo"` } type FooStructForIgnoreFormTag struct { @@ -106,7 +106,7 @@ type InvalidNameType struct { type InvalidNameMapType struct { TestName struct { - MapFoo map[string]interface{} `form:"map_foo"` + MapFoo map[string]any `form:"map_foo"` } } @@ -128,7 +128,7 @@ type FooStructForStructPointerType struct { type FooStructForSliceMapType struct { // Unknown type: not support map - SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` + SliceMapFoo []map[string]any `form:"slice_map_foo"` } type FooStructForBoolType struct { @@ -141,7 +141,7 @@ type FooStructForStringPtrType struct { } type FooStructForMapPtrType struct { - PtrBar *map[string]interface{} `form:"ptr_bar"` + PtrBar *map[string]any `form:"ptr_bar"` } func TestBindingDefault(t *testing.T) { @@ -768,7 +768,7 @@ func TestHeaderBinding(t *testing.T) { req.Header.Add("fail", `{fail:fail}`) type failStruct struct { - Fail map[string]interface{} `header:"fail"` + Fail map[string]any `header:"fail"` } err := h.Bind(req, &failStruct{}) @@ -789,11 +789,11 @@ func TestUriBinding(t *testing.T) { assert.Equal(t, "thinkerou", tag.Name) type NotSupportStruct struct { - Name map[string]interface{} `uri:"name"` + Name map[string]any `uri:"name"` } var not NotSupportStruct assert.Error(t, b.BindUri(m, ¬)) - assert.Equal(t, map[string]interface{}(nil), not.Name) + assert.Equal(t, map[string]any(nil), not.Name) } func TestUriInnerBinding(t *testing.T) { diff --git a/binding/default_validator.go b/binding/default_validator.go index bd8764b..3515a8c 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -46,7 +46,7 @@ func (err SliceValidationError) Error() string { var _ StructValidator = &defaultValidator{} // ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. -func (v *defaultValidator) ValidateStruct(obj interface{}) error { +func (v *defaultValidator) ValidateStruct(obj any) error { if obj == nil { return nil } @@ -75,7 +75,7 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error { } // validateStruct receives struct type -func (v *defaultValidator) validateStruct(obj interface{}) error { +func (v *defaultValidator) validateStruct(obj any) error { v.lazyinit() return v.validate.Struct(obj) } @@ -84,7 +84,7 @@ func (v *defaultValidator) validateStruct(obj interface{}) error { // Validator instance. This is useful if you want to register custom validations // or struct level validations. See validator GoDoc for more info - // https://pkg.go.dev/github.com/go-playground/validator/v10 -func (v *defaultValidator) Engine() interface{} { +func (v *defaultValidator) Engine() any { v.lazyinit() return v.validate } diff --git a/binding/default_validator_test.go b/binding/default_validator_test.go index ff13010..df7742b 100644 --- a/binding/default_validator_test.go +++ b/binding/default_validator_test.go @@ -54,7 +54,7 @@ func TestDefaultValidator(t *testing.T) { tests := []struct { name string v *defaultValidator - obj interface{} + obj any wantErr bool }{ {"validate nil obj", &defaultValidator{}, nil, false}, diff --git a/binding/form.go b/binding/form.go index fa2a654..f5cbf57 100644 --- a/binding/form.go +++ b/binding/form.go @@ -19,7 +19,7 @@ func (formBinding) Name() string { return "form" } -func (formBinding) Bind(req *http.Request, obj interface{}) error { +func (formBinding) Bind(req *http.Request, obj any) error { if err := req.ParseForm(); err != nil { return err } @@ -36,7 +36,7 @@ func (formPostBinding) Name() string { return "form-urlencoded" } -func (formPostBinding) Bind(req *http.Request, obj interface{}) error { +func (formPostBinding) Bind(req *http.Request, obj any) error { if err := req.ParseForm(); err != nil { return err } @@ -50,7 +50,7 @@ func (formMultipartBinding) Name() string { return "multipart/form-data" } -func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { +func (formMultipartBinding) Bind(req *http.Request, obj any) error { if err := req.ParseMultipartForm(defaultMemory); err != nil { return err } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index fa7ad1b..c24dd55 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -26,24 +26,24 @@ var ( ErrConvertToMapString = errors.New("can not convert to map of strings") ) -func mapURI(ptr interface{}, m map[string][]string) error { +func mapURI(ptr any, m map[string][]string) error { return mapFormByTag(ptr, m, "uri") } -func mapForm(ptr interface{}, form map[string][]string) error { +func mapForm(ptr any, form map[string][]string) error { return mapFormByTag(ptr, form, "form") } -func MapFormWithTag(ptr interface{}, form map[string][]string, tag string) error { +func MapFormWithTag(ptr any, form map[string][]string, tag string) error { return mapFormByTag(ptr, form, tag) } var emptyField = reflect.StructField{} -func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { +func mapFormByTag(ptr any, form map[string][]string, tag string) error { // Check if ptr is a map ptrVal := reflect.ValueOf(ptr) - var pointed interface{} + var pointed any if ptrVal.Kind() == reflect.Ptr { ptrVal = ptrVal.Elem() pointed = ptrVal.Interface() @@ -73,7 +73,7 @@ func (form formSource) TrySet(value reflect.Value, field reflect.StructField, ta return setByForm(value, field, form, tagValue, opt) } -func mappingByPtr(ptr interface{}, setter setter, tag string) error { +func mappingByPtr(ptr any, setter setter, tag string) error { _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) return err } @@ -376,7 +376,7 @@ func head(str, sep string) (head string, tail string) { return str[:idx], str[idx+len(sep):] } -func setFormMap(ptr interface{}, form map[string][]string) error { +func setFormMap(ptr any, form map[string][]string) error { el := reflect.TypeOf(ptr).Elem() if el.Kind() == reflect.Slice { diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 516554e..78f4df0 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -18,9 +18,9 @@ func TestMappingBaseTypes(t *testing.T) { } for _, tt := range []struct { name string - value interface{} + value any form string - expect interface{} + expect any }{ {"base type", struct{ F int }{}, "9", int(9)}, {"base type", struct{ F int8 }{}, "9", int8(9)}, diff --git a/binding/header.go b/binding/header.go index b99302a..14f525e 100644 --- a/binding/header.go +++ b/binding/header.go @@ -12,7 +12,7 @@ func (headerBinding) Name() string { return "header" } -func (headerBinding) Bind(req *http.Request, obj interface{}) error { +func (headerBinding) Bind(req *http.Request, obj any) error { if err := mapHeader(obj, req.Header); err != nil { return err @@ -21,7 +21,7 @@ func (headerBinding) Bind(req *http.Request, obj interface{}) error { return validate(obj) } -func mapHeader(ptr interface{}, h map[string][]string) error { +func mapHeader(ptr any, h map[string][]string) error { return mappingByPtr(ptr, headerSource(h), "header") } diff --git a/binding/json.go b/binding/json.go index 45aaa49..2e3e1dd 100644 --- a/binding/json.go +++ b/binding/json.go @@ -30,18 +30,18 @@ func (jsonBinding) Name() string { return "json" } -func (jsonBinding) Bind(req *http.Request, obj interface{}) error { +func (jsonBinding) Bind(req *http.Request, obj any) error { if req == nil || req.Body == nil { return errors.New("invalid request") } return decodeJSON(req.Body, obj) } -func (jsonBinding) BindBody(body []byte, obj interface{}) error { +func (jsonBinding) BindBody(body []byte, obj any) error { return decodeJSON(bytes.NewReader(body), obj) } -func decodeJSON(r io.Reader, obj interface{}) error { +func decodeJSON(r io.Reader, obj any) error { decoder := json.NewDecoder(r) if EnableDecoderUseNumber { decoder.UseNumber() diff --git a/binding/msgpack.go b/binding/msgpack.go index 2a44299..6519771 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -21,15 +21,15 @@ func (msgpackBinding) Name() string { return "msgpack" } -func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { +func (msgpackBinding) Bind(req *http.Request, obj any) error { return decodeMsgPack(req.Body, obj) } -func (msgpackBinding) BindBody(body []byte, obj interface{}) error { +func (msgpackBinding) BindBody(body []byte, obj any) error { return decodeMsgPack(bytes.NewReader(body), obj) } -func decodeMsgPack(r io.Reader, obj interface{}) error { +func decodeMsgPack(r io.Reader, obj any) error { cdc := new(codec.MsgpackHandle) if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil { return err diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go index 75600ba..11561c8 100644 --- a/binding/msgpack_test.go +++ b/binding/msgpack_test.go @@ -26,7 +26,7 @@ func TestMsgpackBindingBindBody(t *testing.T) { assert.Equal(t, "FOO", s.Foo) } -func msgpackBody(t *testing.T, obj interface{}) []byte { +func msgpackBody(t *testing.T, obj any) []byte { var bs bytes.Buffer h := &codec.MsgpackHandle{} err := codec.NewEncoder(&bs, h).Encode(obj) diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go index 4aaa60b..545914d 100644 --- a/binding/multipart_form_mapping_test.go +++ b/binding/multipart_form_mapping_test.go @@ -76,7 +76,7 @@ func TestFormMultipartBindingBindError(t *testing.T) { for _, tt := range []struct { name string - s interface{} + s any }{ {"wrong type", &struct { Files int `form:"file"` diff --git a/binding/protobuf.go b/binding/protobuf.go index a4e4715..ace8f65 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -18,7 +18,7 @@ func (protobufBinding) Name() string { return "protobuf" } -func (b protobufBinding) Bind(req *http.Request, obj interface{}) error { +func (b protobufBinding) Bind(req *http.Request, obj any) error { buf, err := ioutil.ReadAll(req.Body) if err != nil { return err @@ -26,7 +26,7 @@ func (b protobufBinding) Bind(req *http.Request, obj interface{}) error { return b.BindBody(buf, obj) } -func (protobufBinding) BindBody(body []byte, obj interface{}) error { +func (protobufBinding) BindBody(body []byte, obj any) error { msg, ok := obj.(proto.Message) if !ok { return errors.New("obj is not ProtoMessage") diff --git a/binding/query.go b/binding/query.go index 219743f..9790ce6 100644 --- a/binding/query.go +++ b/binding/query.go @@ -12,7 +12,7 @@ func (queryBinding) Name() string { return "query" } -func (queryBinding) Bind(req *http.Request, obj interface{}) error { +func (queryBinding) Bind(req *http.Request, obj any) error { values := req.URL.Query() if err := mapForm(obj, values); err != nil { return err diff --git a/binding/uri.go b/binding/uri.go index a3c0df5..4d44ab9 100644 --- a/binding/uri.go +++ b/binding/uri.go @@ -10,7 +10,7 @@ func (uriBinding) Name() string { return "uri" } -func (uriBinding) BindUri(m map[string][]string, obj interface{}) error { +func (uriBinding) BindUri(m map[string][]string, obj any) error { if err := mapURI(obj, m); err != nil { return err } diff --git a/binding/validate_test.go b/binding/validate_test.go index 5299fbf..d05c891 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -59,7 +59,7 @@ type structNoValidationValues struct { StructSlice []substructNoValidation InterfaceSlice []testInterface - UniversalInterface interface{} + UniversalInterface any CustomInterface testInterface FloatMap map[string]float32 @@ -169,7 +169,7 @@ func TestValidateNoValidationPointers(t *testing.T) { //assert.Equal(t, origin, test) } -type Object map[string]interface{} +type Object map[string]any func TestValidatePrimitives(t *testing.T) { obj := Object{"foo": "bar", "bar": 1} diff --git a/binding/xml.go b/binding/xml.go index 4e90114..e62af4a 100644 --- a/binding/xml.go +++ b/binding/xml.go @@ -17,14 +17,14 @@ func (xmlBinding) Name() string { return "xml" } -func (xmlBinding) Bind(req *http.Request, obj interface{}) error { +func (xmlBinding) Bind(req *http.Request, obj any) error { return decodeXML(req.Body, obj) } -func (xmlBinding) BindBody(body []byte, obj interface{}) error { +func (xmlBinding) BindBody(body []byte, obj any) error { return decodeXML(bytes.NewReader(body), obj) } -func decodeXML(r io.Reader, obj interface{}) error { +func decodeXML(r io.Reader, obj any) error { decoder := xml.NewDecoder(r) if err := decoder.Decode(obj); err != nil { return err diff --git a/binding/yaml.go b/binding/yaml.go index a2d36d6..183f141 100644 --- a/binding/yaml.go +++ b/binding/yaml.go @@ -18,15 +18,15 @@ func (yamlBinding) Name() string { return "yaml" } -func (yamlBinding) Bind(req *http.Request, obj interface{}) error { +func (yamlBinding) Bind(req *http.Request, obj any) error { return decodeYAML(req.Body, obj) } -func (yamlBinding) BindBody(body []byte, obj interface{}) error { +func (yamlBinding) BindBody(body []byte, obj any) error { return decodeYAML(bytes.NewReader(body), obj) } -func decodeYAML(r io.Reader, obj interface{}) error { +func decodeYAML(r io.Reader, obj any) error { decoder := yaml.NewDecoder(r) if err := decoder.Decode(obj); err != nil { return err diff --git a/context.go b/context.go index 48d7550..faa4813 100644 --- a/context.go +++ b/context.go @@ -62,7 +62,7 @@ type Context struct { mu sync.RWMutex // Keys is a key/value pair exclusively for the context of each request. - Keys map[string]interface{} + Keys map[string]any // Errors is a list of errors attached to all the handlers/middlewares who used this context. Errors errorMsgs @@ -115,7 +115,7 @@ func (c *Context) Copy() *Context { cp.Writer = &cp.writermem cp.index = abortIndex cp.handlers = nil - cp.Keys = map[string]interface{}{} + cp.Keys = map[string]any{} for k, v := range c.Keys { cp.Keys[k] = v } @@ -194,7 +194,7 @@ func (c *Context) AbortWithStatus(code int) { // AbortWithStatusJSON calls `Abort()` and then `JSON` internally. // This method stops the chain, writes the status code and return a JSON body. // It also sets the Content-Type as "application/json". -func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) { +func (c *Context) AbortWithStatusJSON(code int, jsonObj any) { c.Abort() c.JSON(code, jsonObj) } @@ -240,10 +240,10 @@ func (c *Context) Error(err error) *Error { // Set is used to store a new key/value pair exclusively for this context. // It also lazy initializes c.Keys if it was not used previously. -func (c *Context) Set(key string, value interface{}) { +func (c *Context) Set(key string, value any) { c.mu.Lock() if c.Keys == nil { - c.Keys = make(map[string]interface{}) + c.Keys = make(map[string]any) } c.Keys[key] = value @@ -252,7 +252,7 @@ func (c *Context) Set(key string, value interface{}) { // Get returns the value for the given key, ie: (value, true). // If the value does not exist it returns (nil, false) -func (c *Context) Get(key string) (value interface{}, exists bool) { +func (c *Context) Get(key string) (value any, exists bool) { c.mu.RLock() value, exists = c.Keys[key] c.mu.RUnlock() @@ -260,7 +260,7 @@ func (c *Context) Get(key string) (value interface{}, exists bool) { } // MustGet returns the value for the given key if it exists, otherwise it panics. -func (c *Context) MustGet(key string) interface{} { +func (c *Context) MustGet(key string) any { if value, exists := c.Get(key); exists { return value } @@ -348,9 +348,9 @@ func (c *Context) GetStringSlice(key string) (ss []string) { } // GetStringMap returns the value associated with the key as a map of interfaces. -func (c *Context) GetStringMap(key string) (sm map[string]interface{}) { +func (c *Context) GetStringMap(key string) (sm map[string]any) { if val, ok := c.Get(key); ok && val != nil { - sm, _ = val.(map[string]interface{}) + sm, _ = val.(map[string]any) } return } @@ -607,39 +607,39 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. // It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. -func (c *Context) Bind(obj interface{}) error { +func (c *Context) Bind(obj any) error { b := binding.Default(c.Request.Method, c.ContentType()) return c.MustBindWith(obj, b) } // BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON). -func (c *Context) BindJSON(obj interface{}) error { +func (c *Context) BindJSON(obj any) error { return c.MustBindWith(obj, binding.JSON) } // BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML). -func (c *Context) BindXML(obj interface{}) error { +func (c *Context) BindXML(obj any) error { return c.MustBindWith(obj, binding.XML) } // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). -func (c *Context) BindQuery(obj interface{}) error { +func (c *Context) BindQuery(obj any) error { return c.MustBindWith(obj, binding.Query) } // BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML). -func (c *Context) BindYAML(obj interface{}) error { +func (c *Context) BindYAML(obj any) error { return c.MustBindWith(obj, binding.YAML) } // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). -func (c *Context) BindHeader(obj interface{}) error { +func (c *Context) BindHeader(obj any) error { return c.MustBindWith(obj, binding.Header) } // BindUri binds the passed struct pointer using binding.Uri. // It will abort the request with HTTP 400 if any error occurs. -func (c *Context) BindUri(obj interface{}) error { +func (c *Context) BindUri(obj any) error { if err := c.ShouldBindUri(obj); err != nil { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err @@ -650,7 +650,7 @@ func (c *Context) BindUri(obj interface{}) error { // MustBindWith binds the passed struct pointer using the specified binding engine. // It will abort the request with HTTP 400 if any error occurs. // See the binding package. -func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { +func (c *Context) MustBindWith(obj any, b binding.Binding) error { if err := c.ShouldBindWith(obj, b); err != nil { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err @@ -665,38 +665,38 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. // Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid. -func (c *Context) ShouldBind(obj interface{}) error { +func (c *Context) ShouldBind(obj any) error { b := binding.Default(c.Request.Method, c.ContentType()) return c.ShouldBindWith(obj, b) } // ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). -func (c *Context) ShouldBindJSON(obj interface{}) error { +func (c *Context) ShouldBindJSON(obj any) error { return c.ShouldBindWith(obj, binding.JSON) } // ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). -func (c *Context) ShouldBindXML(obj interface{}) error { +func (c *Context) ShouldBindXML(obj any) error { return c.ShouldBindWith(obj, binding.XML) } // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). -func (c *Context) ShouldBindQuery(obj interface{}) error { +func (c *Context) ShouldBindQuery(obj any) error { return c.ShouldBindWith(obj, binding.Query) } // ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML). -func (c *Context) ShouldBindYAML(obj interface{}) error { +func (c *Context) ShouldBindYAML(obj any) error { return c.ShouldBindWith(obj, binding.YAML) } // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). -func (c *Context) ShouldBindHeader(obj interface{}) error { +func (c *Context) ShouldBindHeader(obj any) error { return c.ShouldBindWith(obj, binding.Header) } // ShouldBindUri binds the passed struct pointer using the specified binding engine. -func (c *Context) ShouldBindUri(obj interface{}) error { +func (c *Context) ShouldBindUri(obj any) error { m := make(map[string][]string) for _, v := range c.Params { m[v.Key] = []string{v.Value} @@ -706,7 +706,7 @@ func (c *Context) ShouldBindUri(obj interface{}) error { // ShouldBindWith binds the passed struct pointer using the specified binding engine. // See the binding package. -func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { +func (c *Context) ShouldBindWith(obj any, b binding.Binding) error { return b.Bind(c.Request, obj) } @@ -715,7 +715,7 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { // // NOTE: This method reads the body before binding. So you should use // ShouldBindWith for better performance if you need to call only once. -func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) { +func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) { var body []byte if cb, ok := c.Get(BodyBytesKey); ok { if cbb, ok := cb.([]byte); ok { @@ -900,7 +900,7 @@ func (c *Context) Render(code int, r render.Render) { // HTML renders the HTTP template specified by its file name. // It also updates the HTTP code and sets the Content-Type as "text/html". // See http://golang.org/doc/articles/wiki/ -func (c *Context) HTML(code int, name string, obj interface{}) { +func (c *Context) HTML(code int, name string, obj any) { instance := c.engine.HTMLRender.Instance(name, obj) c.Render(code, instance) } @@ -909,21 +909,21 @@ func (c *Context) HTML(code int, name string, obj interface{}) { // It also sets the Content-Type as "application/json". // WARNING: we recommend using this only for development purposes since printing pretty JSON is // more CPU and bandwidth consuming. Use Context.JSON() instead. -func (c *Context) IndentedJSON(code int, obj interface{}) { +func (c *Context) IndentedJSON(code int, obj any) { c.Render(code, render.IndentedJSON{Data: obj}) } // SecureJSON serializes the given struct as Secure JSON into the response body. // Default prepends "while(1)," to response body if the given struct is array values. // It also sets the Content-Type as "application/json". -func (c *Context) SecureJSON(code int, obj interface{}) { +func (c *Context) SecureJSON(code int, obj any) { c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj}) } // JSONP serializes the given struct as JSON into the response body. // It adds padding to response body to request data from a server residing in a different domain than the client. // It also sets the Content-Type as "application/javascript". -func (c *Context) JSONP(code int, obj interface{}) { +func (c *Context) JSONP(code int, obj any) { callback := c.DefaultQuery("callback", "") if callback == "" { c.Render(code, render.JSON{Data: obj}) @@ -934,40 +934,40 @@ func (c *Context) JSONP(code int, obj interface{}) { // JSON serializes the given struct as JSON into the response body. // It also sets the Content-Type as "application/json". -func (c *Context) JSON(code int, obj interface{}) { +func (c *Context) JSON(code int, obj any) { c.Render(code, render.JSON{Data: obj}) } // AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. // It also sets the Content-Type as "application/json". -func (c *Context) AsciiJSON(code int, obj interface{}) { +func (c *Context) AsciiJSON(code int, obj any) { c.Render(code, render.AsciiJSON{Data: obj}) } // PureJSON serializes the given struct as JSON into the response body. // PureJSON, unlike JSON, does not replace special html characters with their unicode entities. -func (c *Context) PureJSON(code int, obj interface{}) { +func (c *Context) PureJSON(code int, obj any) { c.Render(code, render.PureJSON{Data: obj}) } // XML serializes the given struct as XML into the response body. // It also sets the Content-Type as "application/xml". -func (c *Context) XML(code int, obj interface{}) { +func (c *Context) XML(code int, obj any) { c.Render(code, render.XML{Data: obj}) } // YAML serializes the given struct as YAML into the response body. -func (c *Context) YAML(code int, obj interface{}) { +func (c *Context) YAML(code int, obj any) { c.Render(code, render.YAML{Data: obj}) } // ProtoBuf serializes the given struct as ProtoBuf into the response body. -func (c *Context) ProtoBuf(code int, obj interface{}) { +func (c *Context) ProtoBuf(code int, obj any) { c.Render(code, render.ProtoBuf{Data: obj}) } // String writes the given string into the response body. -func (c *Context) String(code int, format string, values ...interface{}) { +func (c *Context) String(code int, format string, values ...any) { c.Render(code, render.String{Format: format, Data: values}) } @@ -1026,7 +1026,7 @@ func (c *Context) FileAttachment(filepath, filename string) { } // SSEvent writes a Server-Sent Event into the body stream. -func (c *Context) SSEvent(name string, message interface{}) { +func (c *Context) SSEvent(name string, message any) { c.Render(-1, sse.Event{ Event: name, Data: message, @@ -1060,11 +1060,11 @@ func (c *Context) Stream(step func(w io.Writer) bool) bool { type Negotiate struct { Offered []string HTMLName string - HTMLData interface{} - JSONData interface{} - XMLData interface{} - YAMLData interface{} - Data interface{} + HTMLData any + JSONData any + XMLData any + YAMLData any + Data any } // Negotiate calls different Render according to acceptable Accept format. @@ -1158,7 +1158,7 @@ func (c *Context) Err() error { // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. -func (c *Context) Value(key interface{}) interface{} { +func (c *Context) Value(key any) any { if key == 0 { return c.Request } diff --git a/context_test.go b/context_test.go index 4eed164..fb46e67 100644 --- a/context_test.go +++ b/context_test.go @@ -212,7 +212,7 @@ func TestContextSetGetValues(t *testing.T) { c.Set("uint64", uint64(42)) c.Set("float32", float32(4.2)) c.Set("float64", 4.2) - var a interface{} = 1 + var a any = 1 c.Set("intInterface", a) assert.Exactly(t, c.MustGet("string").(string), "this is a string") @@ -287,7 +287,7 @@ func TestContextGetStringSlice(t *testing.T) { func TestContextGetStringMap(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - m := make(map[string]interface{}) + m := make(map[string]any) m["foo"] = 1 c.Set("map", m) @@ -2125,12 +2125,12 @@ type contextKey string func TestContextWithFallbackValueFromRequestContext(t *testing.T) { tests := []struct { name string - getContextAndKey func() (*Context, interface{}) - value interface{} + getContextAndKey func() (*Context, any) + value any }{ { name: "c with struct context key", - getContextAndKey: func() (*Context, interface{}) { + getContextAndKey: func() (*Context, any) { var key struct{} c := &Context{} c.Request, _ = http.NewRequest("POST", "/", nil) @@ -2141,7 +2141,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { }, { name: "c with string context key", - getContextAndKey: func() (*Context, interface{}) { + getContextAndKey: func() (*Context, any) { c := &Context{} c.Request, _ = http.NewRequest("POST", "/", nil) c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value")) @@ -2151,7 +2151,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { }, { name: "c with nil http.Request", - getContextAndKey: func() (*Context, interface{}) { + getContextAndKey: func() (*Context, any) { c := &Context{} return c, "key" }, @@ -2159,7 +2159,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { }, { name: "c with nil http.Request.Context()", - getContextAndKey: func() (*Context, interface{}) { + getContextAndKey: func() (*Context, any) { c := &Context{} c.Request, _ = http.NewRequest("POST", "/", nil) return c, "key" diff --git a/debug.go b/debug.go index ed31386..badcb5e 100644 --- a/debug.go +++ b/debug.go @@ -47,7 +47,7 @@ func debugPrintLoadTemplate(tmpl *template.Template) { } } -func debugPrint(format string, values ...interface{}) { +func debugPrint(format string, values ...any) { if IsDebugging() { if !strings.HasSuffix(format, "\n") { format += "\n" diff --git a/deprecated.go b/deprecated.go index ab44742..004e13d 100644 --- a/deprecated.go +++ b/deprecated.go @@ -12,7 +12,7 @@ import ( // BindWith binds the passed struct pointer using the specified binding engine. // See the binding package. -func (c *Context) BindWith(obj interface{}, b binding.Binding) error { +func (c *Context) BindWith(obj any, b binding.Binding) error { log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to be deprecated, please check issue #662 and either use MustBindWith() if you want HTTP 400 to be automatically returned if any error occur, or use diff --git a/errors.go b/errors.go index 3418cbc..578e857 100644 --- a/errors.go +++ b/errors.go @@ -34,7 +34,7 @@ const ( type Error struct { Err error Type ErrorType - Meta interface{} + Meta any } type errorMsgs []*Error @@ -48,13 +48,13 @@ func (msg *Error) SetType(flags ErrorType) *Error { } // SetMeta sets the error's meta data. -func (msg *Error) SetMeta(data interface{}) *Error { +func (msg *Error) SetMeta(data any) *Error { msg.Meta = data return msg } // JSON creates a properly formatted JSON -func (msg *Error) JSON() interface{} { +func (msg *Error) JSON() any { jsonData := H{} if msg.Meta != nil { value := reflect.ValueOf(msg.Meta) @@ -139,14 +139,14 @@ func (a errorMsgs) Errors() []string { return errorStrings } -func (a errorMsgs) JSON() interface{} { +func (a errorMsgs) JSON() any { switch length := len(a); length { case 0: return nil case 1: return a.Last().JSON() default: - jsonData := make([]interface{}, length) + jsonData := make([]any, length) for i, err := range a { jsonData[i] = err.JSON() } diff --git a/errors_test.go b/errors_test.go index 9a17d85..ac72dc4 100644 --- a/errors_test.go +++ b/errors_test.go @@ -86,7 +86,7 @@ Error #02: second Error #03: third Meta: map[status:400] `, errs.String()) - assert.Equal(t, []interface{}{ + assert.Equal(t, []any{ H{"error": "first"}, H{"error": "second", "meta": "some data"}, H{"error": "third", "status": "400"}, diff --git a/gin.go b/gin.go index b54dbd8..b0e0154 100644 --- a/gin.go +++ b/gin.go @@ -198,7 +198,7 @@ func New() *Engine { trustedCIDRs: defaultTrustedCIDRs, } engine.RouterGroup.engine = engine - engine.pool.New = func() interface{} { + engine.pool.New = func() any { return engine.allocateContext() } return engine diff --git a/gin_test.go b/gin_test.go index ae1762e..0c11134 100644 --- a/gin_test.go +++ b/gin_test.go @@ -43,7 +43,7 @@ func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) }) router.GET("/raw", func(c *Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + c.HTML(http.StatusOK, "raw.tmpl", map[string]any{ "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) @@ -467,7 +467,7 @@ func TestNoMethodWithGlobalHandlers(t *testing.T) { compareFunc(t, router.allNoMethod[2], middleware0) } -func compareFunc(t *testing.T, a, b interface{}) { +func compareFunc(t *testing.T, a, b any) { sf1 := reflect.ValueOf(a) sf2 := reflect.ValueOf(b) if sf1.Pointer() != sf2.Pointer() { diff --git a/logger.go b/logger.go index 61b8454..ab43d11 100644 --- a/logger.go +++ b/logger.go @@ -75,7 +75,7 @@ type LogFormatterParams struct { // BodySize is the size of the Response Body BodySize int // Keys are the keys set on the request's context. - Keys map[string]interface{} + Keys map[string]any } // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. diff --git a/logger_test.go b/logger_test.go index ec5e6cd..da1b654 100644 --- a/logger_test.go +++ b/logger_test.go @@ -181,7 +181,7 @@ func TestLoggerWithFormatter(t *testing.T) { func TestLoggerWithConfigFormatting(t *testing.T) { var gotParam LogFormatterParams - var gotKeys map[string]interface{} + var gotKeys map[string]any buffer := new(bytes.Buffer) router := New() diff --git a/recovery.go b/recovery.go index 40eba3b..3efe146 100644 --- a/recovery.go +++ b/recovery.go @@ -28,7 +28,7 @@ var ( ) // RecoveryFunc defines the function passable to CustomRecovery. -type RecoveryFunc func(c *Context, err interface{}) +type RecoveryFunc func(c *Context, err any) // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one. func Recovery() HandlerFunc { @@ -102,7 +102,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } -func defaultHandleRecovery(c *Context, err interface{}) { +func defaultHandleRecovery(c *Context, err any) { c.AbortWithStatus(http.StatusInternalServerError) } diff --git a/recovery_test.go b/recovery_test.go index ac3669a..2c327a6 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -148,7 +148,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) { errBuffer := new(bytes.Buffer) buffer := new(bytes.Buffer) router := New() - handleRecovery := func(c *Context, err interface{}) { + handleRecovery := func(c *Context, err any) { errBuffer.WriteString(err.(string)) c.AbortWithStatus(http.StatusBadRequest) } @@ -183,7 +183,7 @@ func TestCustomRecovery(t *testing.T) { buffer := new(bytes.Buffer) router := New() DefaultErrorWriter = buffer - handleRecovery := func(c *Context, err interface{}) { + handleRecovery := func(c *Context, err any) { errBuffer.WriteString(err.(string)) c.AbortWithStatus(http.StatusBadRequest) } @@ -218,7 +218,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { buffer := new(bytes.Buffer) router := New() DefaultErrorWriter = buffer - handleRecovery := func(c *Context, err interface{}) { + handleRecovery := func(c *Context, err any) { errBuffer.WriteString(err.(string)) c.AbortWithStatus(http.StatusBadRequest) } diff --git a/render/any.go b/render/any.go new file mode 100644 index 0000000..57349be --- /dev/null +++ b/render/any.go @@ -0,0 +1,10 @@ +// Copyright 2021 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build !go1.18 +// +build !go1.18 + +package render + +type any = interface{} diff --git a/render/html.go b/render/html.go index 6696ece..bdfaf11 100644 --- a/render/html.go +++ b/render/html.go @@ -20,7 +20,7 @@ type Delims struct { // HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug. type HTMLRender interface { // Instance returns an HTML instance. - Instance(string, interface{}) Render + Instance(string, any) Render } // HTMLProduction contains template reference and its delims. @@ -41,13 +41,13 @@ type HTMLDebug struct { type HTML struct { Template *template.Template Name string - Data interface{} + Data any } var htmlContentType = []string{"text/html; charset=utf-8"} // Instance (HTMLProduction) returns an HTML instance which it realizes Render interface. -func (r HTMLProduction) Instance(name string, data interface{}) Render { +func (r HTMLProduction) Instance(name string, data any) Render { return HTML{ Template: r.Template, Name: name, @@ -56,7 +56,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render { } // Instance (HTMLDebug) returns an HTML instance which it realizes Render interface. -func (r HTMLDebug) Instance(name string, data interface{}) Render { +func (r HTMLDebug) Instance(name string, data any) Render { return HTML{ Template: r.loadTemplate(), Name: name, diff --git a/render/json.go b/render/json.go index 3ebcee9..0a7dcef 100644 --- a/render/json.go +++ b/render/json.go @@ -16,34 +16,34 @@ import ( // JSON contains the given interface object. type JSON struct { - Data interface{} + Data any } // IndentedJSON contains the given interface object. type IndentedJSON struct { - Data interface{} + Data any } // SecureJSON contains the given interface object and its prefix. type SecureJSON struct { Prefix string - Data interface{} + Data any } // JsonpJSON contains the given interface object its callback. type JsonpJSON struct { Callback string - Data interface{} + Data any } // AsciiJSON contains the given interface object. type AsciiJSON struct { - Data interface{} + Data any } // PureJSON contains the given interface object. type PureJSON struct { - Data interface{} + Data any } var ( @@ -66,7 +66,7 @@ func (r JSON) WriteContentType(w http.ResponseWriter) { } // WriteJSON marshals the given interface object and writes it with custom ContentType. -func WriteJSON(w http.ResponseWriter, obj interface{}) error { +func WriteJSON(w http.ResponseWriter, obj any) error { writeContentType(w, jsonContentType) jsonBytes, err := json.Marshal(obj) if err != nil { diff --git a/render/msgpack.go b/render/msgpack.go index 7f17ca4..2c0e9aa 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -21,7 +21,7 @@ var ( // MsgPack contains the given interface object. type MsgPack struct { - Data interface{} + Data any } var msgpackContentType = []string{"application/msgpack; charset=utf-8"} @@ -37,7 +37,7 @@ func (r MsgPack) Render(w http.ResponseWriter) error { } // WriteMsgPack writes MsgPack ContentType and encodes the given interface object. -func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { +func WriteMsgPack(w http.ResponseWriter, obj any) error { writeContentType(w, msgpackContentType) var mh codec.MsgpackHandle return codec.NewEncoder(w, &mh).Encode(obj) diff --git a/render/protobuf.go b/render/protobuf.go index 1d2aa87..2e57f5c 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -12,7 +12,7 @@ import ( // ProtoBuf contains the given interface object. type ProtoBuf struct { - Data interface{} + Data any } var protobufContentType = []string{"application/x-protobuf"} diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index d16cf6e..7b3601a 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -21,7 +21,7 @@ import ( func TestRenderMsgPack(t *testing.T) { w := httptest.NewRecorder() - data := map[string]interface{}{ + data := map[string]any{ "foo": "bar", } diff --git a/render/render_test.go b/render/render_test.go index e417731..8b28dc3 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -24,7 +24,7 @@ import ( func TestRenderJSON(t *testing.T) { w := httptest.NewRecorder() - data := map[string]interface{}{ + data := map[string]any{ "foo": "bar", "html": "", } @@ -49,7 +49,7 @@ func TestRenderJSONPanics(t *testing.T) { func TestRenderIndentedJSON(t *testing.T) { w := httptest.NewRecorder() - data := map[string]interface{}{ + data := map[string]any{ "foo": "bar", "bar": "foo", } @@ -72,7 +72,7 @@ func TestRenderIndentedJSONPanics(t *testing.T) { func TestRenderSecureJSON(t *testing.T) { w1 := httptest.NewRecorder() - data := map[string]interface{}{ + data := map[string]any{ "foo": "bar", } @@ -86,7 +86,7 @@ func TestRenderSecureJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() - datas := []map[string]interface{}{{ + datas := []map[string]any{{ "foo": "bar", }, { "bar": "foo", @@ -109,7 +109,7 @@ func TestRenderSecureJSONFail(t *testing.T) { func TestRenderJsonpJSON(t *testing.T) { w1 := httptest.NewRecorder() - data := map[string]interface{}{ + data := map[string]any{ "foo": "bar", } @@ -123,7 +123,7 @@ func TestRenderJsonpJSON(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() - datas := []map[string]interface{}{{ + datas := []map[string]any{{ "foo": "bar", }, { "bar": "foo", @@ -137,7 +137,7 @@ func TestRenderJsonpJSON(t *testing.T) { func TestRenderJsonpJSONError2(t *testing.T) { w := httptest.NewRecorder() - data := map[string]interface{}{ + data := map[string]any{ "foo": "bar", } (JsonpJSON{"", data}).WriteContentType(w) @@ -161,7 +161,7 @@ func TestRenderJsonpJSONFail(t *testing.T) { func TestRenderAsciiJSON(t *testing.T) { w1 := httptest.NewRecorder() - data1 := map[string]interface{}{ + data1 := map[string]any{ "lang": "GO语言", "tag": "
", } @@ -190,7 +190,7 @@ func TestRenderAsciiJSONFail(t *testing.T) { func TestRenderPureJSON(t *testing.T) { w := httptest.NewRecorder() - data := map[string]interface{}{ + data := map[string]any{ "foo": "bar", "html": "", } @@ -200,7 +200,7 @@ func TestRenderPureJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } -type xmlmap map[string]interface{} +type xmlmap map[string]any // Allows type H to be used with xml.Marshal func (h xmlmap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { @@ -244,7 +244,7 @@ b: type fail struct{} // Hook MarshalYAML -func (ft *fail) MarshalYAML() (interface{}, error) { +func (ft *fail) MarshalYAML() (any, error) { return nil, errors.New("fail") } @@ -358,13 +358,13 @@ func TestRenderString(t *testing.T) { (String{ Format: "hello %s %d", - Data: []interface{}{}, + Data: []any{}, }).WriteContentType(w) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) err := (String{ Format: "hola %s %d", - Data: []interface{}{"manu", 2}, + Data: []any{"manu", 2}, }).Render(w) assert.NoError(t, err) @@ -377,7 +377,7 @@ func TestRenderStringLenZero(t *testing.T) { err := (String{ Format: "hola %s %d", - Data: []interface{}{}, + Data: []any{}, }).Render(w) assert.NoError(t, err) @@ -390,7 +390,7 @@ func TestRenderHTMLTemplate(t *testing.T) { templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) htmlRender := HTMLProduction{Template: templ} - instance := htmlRender.Instance("t", map[string]interface{}{ + instance := htmlRender.Instance("t", map[string]any{ "name": "alexandernyquist", }) @@ -406,7 +406,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { templ := template.Must(template.New("").Parse(`Hello {{.name}}`)) htmlRender := HTMLProduction{Template: templ} - instance := htmlRender.Instance("", map[string]interface{}{ + instance := htmlRender.Instance("", map[string]any{ "name": "alexandernyquist", }) @@ -425,7 +425,7 @@ func TestRenderHTMLDebugFiles(t *testing.T) { Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, } - instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{ + instance := htmlRender.Instance("hello.tmpl", map[string]any{ "name": "thinkerou", }) @@ -444,7 +444,7 @@ func TestRenderHTMLDebugGlob(t *testing.T) { Delims: Delims{Left: "{[{", Right: "}]}"}, FuncMap: nil, } - instance := htmlRender.Instance("hello.tmpl", map[string]interface{}{ + instance := htmlRender.Instance("hello.tmpl", map[string]any{ "name": "thinkerou", }) diff --git a/render/text.go b/render/text.go index 461b720..b77a776 100644 --- a/render/text.go +++ b/render/text.go @@ -14,7 +14,7 @@ import ( // String contains the given interface object slice and its format. type String struct { Format string - Data []interface{} + Data []any } var plainContentType = []string{"text/plain; charset=utf-8"} @@ -30,7 +30,7 @@ func (r String) WriteContentType(w http.ResponseWriter) { } // WriteString writes data according to its format and write custom ContentType. -func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) { +func WriteString(w http.ResponseWriter, format string, data []any) (err error) { writeContentType(w, plainContentType) if len(data) > 0 { _, err = fmt.Fprintf(w, format, data...) diff --git a/render/xml.go b/render/xml.go index cc5390a..c396a5a 100644 --- a/render/xml.go +++ b/render/xml.go @@ -11,7 +11,7 @@ import ( // XML contains the given interface object. type XML struct { - Data interface{} + Data any } var xmlContentType = []string{"application/xml; charset=utf-8"} diff --git a/render/yaml.go b/render/yaml.go index 0df7836..0fc7a66 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -12,7 +12,7 @@ import ( // YAML contains the given interface object. type YAML struct { - Data interface{} + Data any } var yamlContentType = []string{"application/x-yaml; charset=utf-8"} diff --git a/testdata/protoexample/any.go b/testdata/protoexample/any.go new file mode 100644 index 0000000..f16864c --- /dev/null +++ b/testdata/protoexample/any.go @@ -0,0 +1,10 @@ +// Copyright 2021 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build !go1.18 +// +build !go1.18 + +package protoexample + +type any = interface{} diff --git a/testdata/protoexample/test.pb.go b/testdata/protoexample/test.pb.go index bf45e02..6687aae 100644 --- a/testdata/protoexample/test.pb.go +++ b/testdata/protoexample/test.pb.go @@ -231,7 +231,7 @@ func file_test_proto_rawDescGZIP() []byte { var file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_test_proto_goTypes = []interface{}{ +var file_test_proto_goTypes = []any{ (FOO)(0), // 0: protoexample.FOO (*Test)(nil), // 1: protoexample.Test (*Test_OptionalGroup)(nil), // 2: protoexample.Test.OptionalGroup @@ -251,7 +251,7 @@ func file_test_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_test_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*Test); i { case 0: return &v.state @@ -263,7 +263,7 @@ func file_test_proto_init() { return nil } } - file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_test_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*Test_OptionalGroup); i { case 0: return &v.state diff --git a/tree_test.go b/tree_test.go index 94c5338..085b580 100644 --- a/tree_test.go +++ b/tree_test.go @@ -357,7 +357,7 @@ func TestUnescapeParameters(t *testing.T) { checkPriorities(t, tree) } -func catchPanic(testFunc func()) (recv interface{}) { +func catchPanic(testFunc func()) (recv any) { defer func() { recv = recover() }() diff --git a/utils.go b/utils.go index e4599ea..b41a3b0 100644 --- a/utils.go +++ b/utils.go @@ -19,7 +19,7 @@ import ( const BindKey = "_gin-gonic/gin/bindkey" // Bind is a helper function for given interface object and returns a Gin middleware. -func Bind(val interface{}) HandlerFunc { +func Bind(val any) HandlerFunc { value := reflect.ValueOf(val) if value.Kind() == reflect.Ptr { panic(`Bind struct can not be a pointer. Example: @@ -51,7 +51,7 @@ func WrapH(h http.Handler) HandlerFunc { } // H is a shortcut for map[string]interface{} -type H map[string]interface{} +type H map[string]any // MarshalXML allows type H to be used with xml.Marshal. func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { @@ -90,7 +90,7 @@ func filterFlags(content string) string { return content } -func chooseData(custom, wildcard interface{}) interface{} { +func chooseData(custom, wildcard any) any { if custom != nil { return custom } @@ -121,7 +121,7 @@ func lastChar(str string) uint8 { return str[len(str)-1] } -func nameOfFunction(f interface{}) string { +func nameOfFunction(f any) string { return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() } From 62265c893c5bd4bb1ba295008809fcc5dbd1d28d Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 21 Mar 2022 10:51:17 +0800 Subject: [PATCH 659/912] chore: support min version of go: 1.14 (#2964) --- .github/workflows/gin.yml | 2 +- README.md | 2 +- debug.go | 4 ++-- debug_test.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 1aecda5..fa9d68b 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.13, 1.14, 1.15, 1.16, 1.17] + go: [1.14, 1.15, 1.16, 1.17] test-tags: ['', nomsgpack] include: - os: ubuntu-latest diff --git a/README.md b/README.md index 9a9785e..4aa638d 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. The first need [Go](https://golang.org/) installed (**version 1.13+ is required**), then you can use the below Go command to install Gin. +1. The first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin diff --git a/debug.go b/debug.go index badcb5e..d8d6c8e 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 13 +const ginSupportMinGoVer = 14 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.13+. + debugPrint(`[WARNING] Now Gin requires Go 1.14+. `) } diff --git a/debug_test.go b/debug_test.go index 0550999..7c54444 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.13+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.14+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } From be0d86edf49cd5984d727961cda85e8557835524 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 21 Mar 2022 17:38:11 +0800 Subject: [PATCH 660/912] chore(CI/CD): add go1.18 version (#3092) * chore(CI/CD): add go1.18 version Signed-off-by: Bo-Yi Wu * Update go.mod * Update go.sum * Update go.mod * Update go.sum Co-authored-by: thinkerou --- .github/workflows/gin.yml | 6 +++--- go.mod | 16 +++++++++++++++- go.sum | 1 - 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index fa9d68b..c432a33 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -21,14 +21,14 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.1.0 with: - version: v1.44.0 + version: v1.45.0 args: --verbose test: needs: lint strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.14, 1.15, 1.16, 1.17] + go: [1.14, 1.15, 1.16, 1.17, 1.18] test-tags: ['', nomsgpack] include: - os: ubuntu-latest @@ -51,7 +51,7 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ github.ref }} - + - uses: actions/cache@v2 with: path: | diff --git a/go.mod b/go.mod index 7a9249c..dcd8368 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gin-gonic/gin -go 1.13 +go 1.18 require ( github.com/gin-contrib/sse v0.1.0 @@ -14,3 +14,17 @@ require ( google.golang.org/protobuf v1.27.1 gopkg.in/yaml.v2 v2.4.0 ) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect + golang.org/x/text v0.3.6 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/go.sum b/go.sum index 6006ffb..c0980ad 100644 --- a/go.sum +++ b/go.sum @@ -47,7 +47,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= From 865fd560fc2f549cabb73452425dec22738c7b2d Mon Sep 17 00:00:00 2001 From: mstmdev Date: Wed, 23 Mar 2022 21:35:09 +0800 Subject: [PATCH 661/912] Update some comments, add function name prefix to comment (#3090) --- gin.go | 27 ++++++++++++++------------- logger.go | 6 +++--- response_writer.go | 16 ++++++++-------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/gin.go b/gin.go index b0e0154..c2e95f2 100644 --- a/gin.go +++ b/gin.go @@ -67,10 +67,10 @@ type RoutesInfo []RouteInfo // Trusted platforms const ( - // When running on Google App Engine. Trust X-Appengine-Remote-Addr + // PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr // for determining the client's IP PlatformGoogleAppEngine = "X-Appengine-Remote-Addr" - // When using Cloudflare's CDN. Trust CF-Connecting-IP for determining + // PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining // the client's IP PlatformCloudflare = "CF-Connecting-IP" ) @@ -80,14 +80,14 @@ const ( type Engine struct { RouterGroup - // Enables automatic redirection if the current route can't be matched but a + // RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a // handler for the path with (without) the trailing slash exists. // For example if /foo/ is requested but a route only exists for /foo, the // client is redirected to /foo with http status code 301 for GET requests // and 307 for all other request methods. RedirectTrailingSlash bool - // If enabled, the router tries to fix the current request path, if no + // RedirectFixedPath if enabled, the router tries to fix the current request path, if no // handle is registered for it. // First superfluous path elements like ../ or // are removed. // Afterwards the router does a case-insensitive lookup of the cleaned path. @@ -98,7 +98,7 @@ type Engine struct { // RedirectTrailingSlash is independent of this option. RedirectFixedPath bool - // If enabled, the router checks if another method is allowed for the + // HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the // current route, if the current request can not be routed. // If this is the case, the request is answered with 'Method Not Allowed' // and HTTP status code 405. @@ -106,21 +106,22 @@ type Engine struct { // handler. HandleMethodNotAllowed bool - // If enabled, client IP will be parsed from the request's headers that + // ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that // match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was // fetched, it falls back to the IP obtained from // `(*gin.Context).Request.RemoteAddr`. ForwardedByClientIP bool - // DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD + // AppEngine was deprecated. + // Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD // #726 #755 If enabled, it will trust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool - // If enabled, the url.RawPath will be used to find parameters. + // UseRawPath if enabled, the url.RawPath will be used to find parameters. UseRawPath bool - // If true, the path value will be unescaped. + // UnescapePathValues 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 @@ -129,21 +130,21 @@ type Engine struct { // See the PR #1817 and issue #1644 RemoveExtraSlash bool - // List of headers used to obtain the client IP when + // RemoteIPHeaders list of headers used to obtain the client IP when // `(*gin.Engine).ForwardedByClientIP` is `true` and // `(*gin.Context).Request.RemoteAddr` is matched by at least one of the // network origins of list defined by `(*gin.Engine).SetTrustedProxies()`. RemoteIPHeaders []string - // If set to a constant of value gin.Platform*, trusts the headers set by + // TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by // that platform, for example to determine the client IP TrustedPlatform string - // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm + // MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm // method call. MaxMultipartMemory int64 - // Enable h2c support. + // UseH2C enable h2c support. UseH2C bool delims render.Delims diff --git a/logger.go b/logger.go index ab43d11..1f9d63a 100644 --- a/logger.go +++ b/logger.go @@ -44,7 +44,7 @@ type LoggerConfig struct { // Optional. Default value is gin.DefaultWriter. Output io.Writer - // SkipPaths is a url path array which logs are not written. + // SkipPaths is an url path array which logs are not written. // Optional. SkipPaths []string } @@ -161,12 +161,12 @@ func ForceConsoleColor() { consoleColorMode = forceColor } -// ErrorLogger returns a handlerfunc for any error type. +// ErrorLogger returns a HandlerFunc for any error type. func ErrorLogger() HandlerFunc { return ErrorLoggerT(ErrorTypeAny) } -// ErrorLoggerT returns a handlerfunc for a given error type. +// ErrorLoggerT returns a HandlerFunc for a given error type. func ErrorLoggerT(typ ErrorType) HandlerFunc { return func(c *Context) { c.Next() diff --git a/response_writer.go b/response_writer.go index 2682668..7f9095f 100644 --- a/response_writer.go +++ b/response_writer.go @@ -23,23 +23,23 @@ type ResponseWriter interface { http.Flusher http.CloseNotifier - // Returns the HTTP response status code of the current request. + // Status returns the HTTP response status code of the current request. Status() int - // Returns the number of bytes already written into the response http body. + // Size returns the number of bytes already written into the response http body. // See Written() Size() int - // Writes the string into the response body. + // WriteString writes the string into the response body. WriteString(string) (int, error) - // Returns true if the response body was already written. + // Written returns true if the response body was already written. Written() bool - // Forces to write the http header (status code + headers). + // WriteHeaderNow forces to write the http header (status code + headers). WriteHeaderNow() - // get the http.Pusher for server push + // Pusher get the http.Pusher for server push Pusher() http.Pusher } @@ -107,12 +107,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { return w.ResponseWriter.(http.Hijacker).Hijack() } -// CloseNotify implements the http.CloseNotify interface. +// CloseNotify implements the http.CloseNotifier interface. func (w *responseWriter) CloseNotify() <-chan bool { return w.ResponseWriter.(http.CloseNotifier).CloseNotify() } -// Flush implements the http.Flush interface. +// Flush implements the http.Flusher interface. func (w *responseWriter) Flush() { w.WriteHeaderNow() w.ResponseWriter.(http.Flusher).Flush() From 205bb8151cb73c51f92d893426e7f82a0b6c1744 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 08:44:37 +0800 Subject: [PATCH 662/912] chore(deps): bump actions/cache from 2 to 3 (#3093) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index c432a33..f67641a 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -52,7 +52,7 @@ jobs: with: ref: ${{ github.ref }} - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ${{ matrix.go-build }} From 3d55efe41962a7f26620444105b98e6778050218 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Mar 2022 12:02:20 +0800 Subject: [PATCH 663/912] chore(deps): bump google.golang.org/protobuf from 1.27.1 to 1.28.0 (#3104) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dcd8368..846bd5d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/stretchr/testify v1.7.0 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 - google.golang.org/protobuf v1.27.1 + google.golang.org/protobuf v1.28.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index c0980ad..c92ce01 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From 6a1d279c283bd8b36877651a02aa86944138d30d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Mar 2022 14:03:30 +0800 Subject: [PATCH 664/912] chore(deps): bump github.com/goccy/go-json from 0.9.5 to 0.9.6 (#3105) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.9.5 to 0.9.6. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.9.5...v0.9.6) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 846bd5d..6c46755 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 - github.com/goccy/go-json v0.9.5 + github.com/goccy/go-json v0.9.6 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index c92ce01..5108a59 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.9.5 h1:ooSMW526ZjK+EaL5elrSyN2EzIfi/3V0m4+HJEDYLik= -github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E= +github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From c4580944ae8edba96809660ffbfdadc6caaefc2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Mar 2022 14:03:54 +0800 Subject: [PATCH 665/912] chore(deps): bump github.com/stretchr/testify from 1.7.0 to 1.7.1 (#3094) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.0 to 1.7.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.7.0...v1.7.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 6c46755..bfb462b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/goccy/go-json v0.9.6 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.1 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 google.golang.org/protobuf v1.28.0 diff --git a/go.sum b/go.sum index 5108a59..983b50a 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,9 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= From 888b14ab283f3623174e99b6ae7555060731cd89 Mon Sep 17 00:00:00 2001 From: "Jonathan (JC) Chen" Date: Fri, 15 Apr 2022 18:52:09 -0700 Subject: [PATCH 666/912] docs: Update README.md (#3108) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4aa638d..af9a1e5 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. The first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin. +1. You first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin From 68542126982eb2275ea0447c58b38cb38cff8ddc Mon Sep 17 00:00:00 2001 From: ahuigo <1781999+ahuigo@users.noreply.github.com> Date: Sun, 17 Apr 2022 12:41:59 +0800 Subject: [PATCH 667/912] Fix: missing `sameSite` when do context.reset() (#3123) --- context.go | 1 + 1 file changed, 1 insertion(+) diff --git a/context.go b/context.go index faa4813..0fe1333 100644 --- a/context.go +++ b/context.go @@ -98,6 +98,7 @@ func (c *Context) reset() { c.Accepted = nil c.queryCache = nil c.formCache = nil + c.sameSite = 0 *c.params = (*c.params)[:0] *c.skippedNodes = (*c.skippedNodes)[:0] } From 493b12482b913115d2664043f7aac0cafa96690a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Apr 2022 14:15:53 +0800 Subject: [PATCH 668/912] chore(deps): bump actions/setup-go from 2 to 3 (#3118) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index f67641a..771c0b9 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: '^1.16' - name: Checkout repository @@ -43,7 +43,7 @@ jobs: GOPROXY: https://proxy.golang.org steps: - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} From 696d37e0306fcb5e7399b61f6971024a431d0a90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Apr 2022 14:16:17 +0800 Subject: [PATCH 669/912] chore(deps): bump codecov/codecov-action from 2 to 3 (#3117) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 771c0b9..c5e2744 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -65,7 +65,7 @@ jobs: run: make test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} notification-gitter: From 444e156fb1ec2943af5308a790315833c74fdc5a Mon Sep 17 00:00:00 2001 From: mstmdev Date: Thu, 21 Apr 2022 18:21:46 +0800 Subject: [PATCH 670/912] Fix some tests (#3100) * Sleep for one millisecond in the handler because the `Latency` will return `0s` sometimes and the test will fail * The `TCPListener.File` is not supported by windows, it is unimplemented now * Remove the `LF` in the `testdata/template/raw.tmpl`, because if set the git config `core.autocrlf=true`, will append `CR` to the raw.tmpl automatically, then test is failed on Windows --- gin_integration_test.go | 16 +++++++++++++++- gin_test.go | 4 ++-- logger_test.go | 1 + testdata/template/raw.tmpl | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index 8c22e7b..0dfa903 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -15,6 +15,7 @@ import ( "net/http/httptest" "os" "path/filepath" + "runtime" "sync" "testing" "time" @@ -281,7 +282,16 @@ func TestFileDescriptor(t *testing.T) { listener, err := net.ListenTCP("tcp", addr) assert.NoError(t, err) socketFile, err := listener.File() - assert.NoError(t, err) + if isWindows() { + // not supported by windows, it is unimplemented now + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + + if socketFile == nil { + return + } go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) @@ -547,3 +557,7 @@ func TestTreeRunDynamicRouting(t *testing.T) { testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found") testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found") } + +func isWindows() bool { + return runtime.GOOS == "windows" +} diff --git a/gin_test.go b/gin_test.go index 0c11134..a438062 100644 --- a/gin_test.go +++ b/gin_test.go @@ -202,7 +202,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "Date: 2017/07/01\n", string(resp)) + assert.Equal(t, "Date: 2017/07/01", string(resp)) } func init() { @@ -320,7 +320,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { } resp, _ := ioutil.ReadAll(res.Body) - assert.Equal(t, "Date: 2017/07/01\n", string(resp)) + assert.Equal(t, "Date: 2017/07/01", string(resp)) } func TestAddRoute(t *testing.T) { diff --git a/logger_test.go b/logger_test.go index da1b654..b704998 100644 --- a/logger_test.go +++ b/logger_test.go @@ -208,6 +208,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) { // set dummy ClientIP c.Request.Header.Set("X-Forwarded-For", "20.20.20.20") gotKeys = c.Keys + time.Sleep(time.Millisecond) }) PerformRequest(router, "GET", "/example?a=100") diff --git a/testdata/template/raw.tmpl b/testdata/template/raw.tmpl index 8bc7570..f3f530a 100644 --- a/testdata/template/raw.tmpl +++ b/testdata/template/raw.tmpl @@ -1 +1 @@ -Date: {[{.now | formatAsDate}]} +Date: {[{.now | formatAsDate}]} \ No newline at end of file From c706ace9298fe6440041ba0cb099ea4b3acda980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20B=C4=85k?= <56700396+53jk1@users.noreply.github.com> Date: Sat, 23 Apr 2022 12:01:03 +0200 Subject: [PATCH 671/912] fix: removed YODA conditions, removed blank identifier from `invalid_obj` (#3129) * fix: removed YODA conditions, unnecessary binding.binding * fix: remove BindingBody change --- binding/binding_test.go | 4 ++-- githubapi_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index b1edbf5..d9746e2 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1339,10 +1339,10 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body err := b.Bind(req, &obj) assert.Error(t, err) - invalid_obj := FooStruct{} + invalidobj := FooStruct{} req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`)) req.Header.Add("Content-Type", MIMEPROTOBUF) - err = b.Bind(req, &invalid_obj) + err = b.Bind(req, &invalidobj) assert.Error(t, err) assert.Equal(t, err.Error(), "obj is not ProtoMessage") diff --git a/githubapi_test.go b/githubapi_test.go index e74bddd..5fe65a4 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -296,8 +296,8 @@ func TestShouldBindUri(t *testing.T) { router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person assert.NoError(t, c.ShouldBindUri(&person)) - assert.True(t, "" != person.Name) - assert.True(t, "" != person.ID) + assert.True(t, person.Name != "") + assert.True(t, person.ID != "") c.String(http.StatusOK, "ShouldBindUri test OK") }) @@ -318,8 +318,8 @@ func TestBindUri(t *testing.T) { router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person assert.NoError(t, c.BindUri(&person)) - assert.True(t, "" != person.Name) - assert.True(t, "" != person.ID) + assert.True(t, person.Name != "") + assert.True(t, person.ID != "") c.String(http.StatusOK, "BindUri test OK") }) From d8e053d15f2fa12f565c10cb92505d6e3e970da4 Mon Sep 17 00:00:00 2001 From: Lanco <35420416+lancoLiu@users.noreply.github.com> Date: Sat, 23 Apr 2022 18:01:41 +0800 Subject: [PATCH 672/912] use StringToBytes func (#2798) --- auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth.go b/auth.go index 4d8a6ce..482c499 100644 --- a/auth.go +++ b/auth.go @@ -31,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) { return "", false } for _, pair := range a { - if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 { + if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 { return pair.user, true } } From e61cc06955d4d76a1781392577d0d1bcc1c60824 Mon Sep 17 00:00:00 2001 From: Faisal Alam Date: Sat, 23 Apr 2022 15:32:54 +0530 Subject: [PATCH 673/912] feat(context): return GIN Context from Value method (#2825) --- context.go | 6 ++++++ context_test.go | 1 + 2 files changed, 7 insertions(+) diff --git a/context.go b/context.go index 0fe1333..5e53aaf 100644 --- a/context.go +++ b/context.go @@ -39,6 +39,9 @@ const ( // BodyBytesKey indicates a default body bytes key. const BodyBytesKey = "_gin-gonic/gin/bodybyteskey" +// ContextKey is the key that a Context returns itself for. +const ContextKey = "_gin-gonic/gin/contextkey" + // abortIndex represents a typical value used in abort functions. const abortIndex int8 = math.MaxInt8 >> 1 @@ -1163,6 +1166,9 @@ func (c *Context) Value(key any) any { if key == 0 { return c.Request } + if key == ContextKey { + return c + } if keyAsString, ok := key.(string); ok { if val, exists := c.Get(keyAsString); exists { return val diff --git a/context_test.go b/context_test.go index fb46e67..20038e9 100644 --- a/context_test.go +++ b/context_test.go @@ -1880,6 +1880,7 @@ func TestContextGolangContext(t *testing.T) { assert.Equal(t, ti, time.Time{}) assert.False(t, ok) assert.Equal(t, c.Value(0), c.Request) + assert.Equal(t, c.Value(ContextKey), c) assert.Nil(t, c.Value("foo")) c.Set("foo", "bar") From c131704fd6f31500f65f4b82073ef58dc4fc2fd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Apr 2022 08:50:28 +0800 Subject: [PATCH 674/912] chore(deps): bump github.com/goccy/go-json from 0.9.6 to 0.9.7 (#3131) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bfb462b..2da1fe3 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 - github.com/goccy/go-json v0.9.6 + github.com/goccy/go-json v0.9.7 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.7.1 diff --git a/go.sum b/go.sum index 983b50a..d7a221f 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E= -github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From 90e7073d56c0d2d8dc74f2325adc5c14527947db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Apr 2022 08:51:13 +0800 Subject: [PATCH 675/912] chore(deps): bump github/codeql-action from 1 to 2 (#3132) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4cbc455..e27022d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -46,4 +46,4 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From ef687e0db2947ddae312cb21bc2cdb8925611714 Mon Sep 17 00:00:00 2001 From: micanzhang Date: Sat, 14 May 2022 09:11:35 +0800 Subject: [PATCH 676/912] feat: automatically SetMode to TestMode when run go test. (#3139) related issue: https://github.com/gin-gonic/gin/issues/3134 --- mode.go | 7 ++++++- mode_test.go | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/mode.go b/mode.go index 1fb994b..4e7d734 100644 --- a/mode.go +++ b/mode.go @@ -5,6 +5,7 @@ package gin import ( + "flag" "io" "os" @@ -54,7 +55,11 @@ func init() { // SetMode sets gin mode according to input string. func SetMode(value string) { if value == "" { - value = DebugMode + if flag.Lookup("test.v") != nil { + value = TestMode + } else { + value = DebugMode + } } switch value { diff --git a/mode_test.go b/mode_test.go index 1b5fb2f..6fd9a13 100644 --- a/mode_test.go +++ b/mode_test.go @@ -5,6 +5,7 @@ package gin import ( + "flag" "os" "testing" @@ -21,9 +22,16 @@ func TestSetMode(t *testing.T) { assert.Equal(t, TestMode, Mode()) os.Unsetenv(EnvGinMode) + SetMode("") + assert.Equal(t, testCode, ginMode) + assert.Equal(t, TestMode, Mode()) + + tmp := flag.CommandLine + flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError) SetMode("") assert.Equal(t, debugCode, ginMode) assert.Equal(t, DebugMode, Mode()) + flag.CommandLine = tmp SetMode(DebugMode) assert.Equal(t, debugCode, ginMode) From f1e942889abdab773538f215ec6aa242f994c6d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 May 2022 22:27:27 +0800 Subject: [PATCH 677/912] chore(deps): bump golangci/golangci-lint-action from 3.1.0 to 3.2.0 (#3150) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.1.0...v3.2.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index c5e2744..e8ef30c 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -19,7 +19,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.1.0 + uses: golangci/golangci-lint-action@v3.2.0 with: version: v1.45.0 args: --verbose From 87811a97bddbe552ddd7f5ae2fa7bb949e787862 Mon Sep 17 00:00:00 2001 From: Eric_Lee Date: Sat, 28 May 2022 08:14:35 +0800 Subject: [PATCH 678/912] fix: the trusted proxies should support ipv6 address by default (#3033) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index c2e95f2..3a831e5 100644 --- a/gin.go +++ b/gin.go @@ -195,7 +195,7 @@ func New() *Engine { trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJSONPrefix: "while(1);", - trustedProxies: []string{"0.0.0.0/0"}, + trustedProxies: []string{"0.0.0.0/0", "::/0"}, trustedCIDRs: defaultTrustedCIDRs, } engine.RouterGroup.engine = engine From aa6002134e97efefd879b6819c1bfce114b05f42 Mon Sep 17 00:00:00 2001 From: Thibault Jamet Date: Sat, 28 May 2022 02:27:10 +0200 Subject: [PATCH 679/912] Fix intercepting headers in middlewares (#1271) * Fix intercepting headers in middlewares As explained in the TestInterceptedHeader test, in case a middleware filters out the headers, this middleware can be done inefficient in case one following handler is using c.String or other methods writing to the response body directly. This commit fixes the issue by using c.Writer when writing the Status as done in other c.Header, c.SetCookie and other response writers. The bug has been originally discovered using https://github.com/gin-contrib/gzip where a failing test has been added here: https://github.com/tjamet/gzip/blob/header/gzip_test.go#L71 Signed-off-by: Thibault Jamet * Skip Intercepted Header test for go <1.6 Signed-off-by: Thibault Jamet --- context_go17_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 context_go17_test.go diff --git a/context_go17_test.go b/context_go17_test.go new file mode 100644 index 0000000..eca089c --- /dev/null +++ b/context_go17_test.go @@ -0,0 +1,50 @@ +// +build go1.7 + +package gin + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +type interceptedWriter struct { + ResponseWriter + b *bytes.Buffer +} + +func (i interceptedWriter) WriteHeader(code int) { + i.Header().Del("X-Test") + i.ResponseWriter.WriteHeader(code) +} +func TestInterceptedHeader(t *testing.T) { + w := httptest.NewRecorder() + c, r := CreateTestContext(w) + + r.Use(func(c *Context) { + i := interceptedWriter{ + ResponseWriter: c.Writer, + b: bytes.NewBuffer(nil), + } + c.Writer = i + c.Next() + c.Header("X-Test", "overridden") + c.Writer = i.ResponseWriter + }) + r.GET("/", func(c *Context) { + c.Header("X-Test", "original") + c.Header("X-Test-2", "present") + c.String(http.StatusOK, "hello world") + }) + c.Request = httptest.NewRequest("GET", "/", nil) + r.HandleContext(c) + // Result() has headers frozen when WriteHeaderNow() has been called + // Compared to this time, this is when the response headers will be flushed + // As response is flushed on c.String, the Header cannot be set by the first + // middleware. Assert this + assert.Equal(t, "", w.Result().Header.Get("X-Test")) + assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) +} From ed03102ef0b08c552bc3d0a73fbd98307461abcc Mon Sep 17 00:00:00 2001 From: Valentine Oragbakosi Date: Fri, 27 May 2022 16:34:43 -0800 Subject: [PATCH 680/912] [GIN-001] - Add TOML bining for gin (#3081) Co-authored-by: GitstartHQ --- README.md | 6 +++--- binding/binding.go | 4 ++++ binding/binding_nomsgpack.go | 4 ++++ binding/binding_test.go | 17 +++++++++++++++++ binding/toml.go | 31 +++++++++++++++++++++++++++++++ binding/toml_test.go | 22 ++++++++++++++++++++++ context.go | 21 +++++++++++++++++++++ context_test.go | 17 +++++++++++++++++ go.mod | 1 + go.sum | 4 ++++ render/render.go | 1 + render/toml.go | 32 ++++++++++++++++++++++++++++++++ 12 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 binding/toml.go create mode 100644 binding/toml_test.go create mode 100644 render/toml.go diff --git a/README.md b/README.md index af9a1e5..6b4cabb 100644 --- a/README.md +++ b/README.md @@ -658,7 +658,7 @@ func main() { ### Model binding and validation -To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). +To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz). Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). @@ -666,10 +666,10 @@ Note that you need to set the corresponding binding tag on all fields you want t Also, Gin provides two sets of methods for binding: - **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader` + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML` - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Type** - Should bind - - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader` + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`, - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. diff --git a/binding/binding.go b/binding/binding.go index 703a1cf..5051051 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -22,6 +22,7 @@ const ( MIMEMSGPACK = "application/x-msgpack" MIMEMSGPACK2 = "application/msgpack" MIMEYAML = "application/x-yaml" + MIMETOML = "application/toml" ) // Binding describes the interface which needs to be implemented for binding the @@ -83,6 +84,7 @@ var ( YAML = yamlBinding{} Uri = uriBinding{} Header = headerBinding{} + TOML = tomlBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method @@ -103,6 +105,8 @@ func Default(method, contentType string) Binding { return MsgPack case MIMEYAML: return YAML + case MIMETOML: + return TOML case MIMEMultipartPOSTForm: return FormMultipart default: // case MIMEPOSTForm: diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index b381854..7f6a904 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -20,6 +20,7 @@ const ( MIMEMultipartPOSTForm = "multipart/form-data" MIMEPROTOBUF = "application/x-protobuf" MIMEYAML = "application/x-yaml" + MIMETOML = "application/toml" ) // Binding describes the interface which needs to be implemented for binding the @@ -79,6 +80,7 @@ var ( YAML = yamlBinding{} Uri = uriBinding{} Header = headerBinding{} + TOML = tomlBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method @@ -99,6 +101,8 @@ func Default(method, contentType string) Binding { return YAML case MIMEMultipartPOSTForm: return FormMultipart + case MIMETOML: + return TOML default: // case MIMEPOSTForm: return Form } diff --git a/binding/binding_test.go b/binding/binding_test.go index d9746e2..f08e173 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -165,6 +165,9 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, YAML, Default("POST", MIMEYAML)) assert.Equal(t, YAML, Default("PUT", MIMEYAML)) + + assert.Equal(t, TOML, Default("POST", MIMETOML)) + assert.Equal(t, TOML, Default("PUT", MIMETOML)) } func TestBindingJSONNilBody(t *testing.T) { @@ -454,6 +457,20 @@ func TestBindingXMLFail(t *testing.T) { "bar", "foo") } +func TestBindingTOML(t *testing.T) { + testBodyBinding(t, + TOML, "toml", + "/", "/", + `foo="bar"`, `bar="foo"`) +} + +func TestBindingTOMLFail(t *testing.T) { + testBodyBindingFail(t, + TOML, "toml", + "/", "/", + `foo=\n"bar"`, `bar="foo"`) +} + func TestBindingYAML(t *testing.T) { testBodyBinding(t, YAML, "yaml", diff --git a/binding/toml.go b/binding/toml.go new file mode 100644 index 0000000..5b9ad16 --- /dev/null +++ b/binding/toml.go @@ -0,0 +1,31 @@ +package binding + +import ( + "bytes" + "io" + "net/http" + + "github.com/pelletier/go-toml/v2" +) + +type tomlBinding struct{} + +func (tomlBinding) Name() string { + return "toml" +} + +func decodeToml(r io.Reader, obj any) error { + decoder := toml.NewDecoder(r) + if err := decoder.Decode(obj); err != nil { + return err + } + return decoder.Decode(obj) +} + +func (tomlBinding) Bind(req *http.Request, obj any) error { + return decodeToml(req.Body, obj) +} + +func (tomlBinding) BindBody(body []byte, obj any) error { + return decodeToml(bytes.NewReader(body), obj) +} diff --git a/binding/toml_test.go b/binding/toml_test.go new file mode 100644 index 0000000..2bc0e3a --- /dev/null +++ b/binding/toml_test.go @@ -0,0 +1,22 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTOMLBindingBindBody(t *testing.T) { + var s struct { + Foo string `toml:"foo"` + } + tomlBody := `foo="FOO"` + err := tomlBinding{}.BindBody([]byte(tomlBody), &s) + require.NoError(t, err) + assert.Equal(t, "FOO", s.Foo) +} diff --git a/context.go b/context.go index 5e53aaf..ecbd3da 100644 --- a/context.go +++ b/context.go @@ -34,6 +34,7 @@ const ( MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm MIMEYAML = binding.MIMEYAML + MIMETOML = binding.MIMETOML ) // BodyBytesKey indicates a default body bytes key. @@ -636,6 +637,11 @@ func (c *Context) BindYAML(obj any) error { return c.MustBindWith(obj, binding.YAML) } +// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML). +func (c *Context) BindTOML(obj interface{}) error { + return c.MustBindWith(obj, binding.TOML) +} + // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). func (c *Context) BindHeader(obj any) error { return c.MustBindWith(obj, binding.Header) @@ -694,6 +700,11 @@ func (c *Context) ShouldBindYAML(obj any) error { return c.ShouldBindWith(obj, binding.YAML) } +// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML). +func (c *Context) ShouldBindTOML(obj interface{}) error { + return c.ShouldBindWith(obj, binding.TOML) +} + // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). func (c *Context) ShouldBindHeader(obj any) error { return c.ShouldBindWith(obj, binding.Header) @@ -965,6 +976,11 @@ func (c *Context) YAML(code int, obj any) { c.Render(code, render.YAML{Data: obj}) } +// TOML serializes the given struct as TOML into the response body. +func (c *Context) TOML(code int, obj interface{}) { + c.Render(code, render.TOML{Data: obj}) +} + // ProtoBuf serializes the given struct as ProtoBuf into the response body. func (c *Context) ProtoBuf(code int, obj any) { c.Render(code, render.ProtoBuf{Data: obj}) @@ -1069,6 +1085,7 @@ type Negotiate struct { XMLData any YAMLData any Data any + TOMLData any } // Negotiate calls different Render according to acceptable Accept format. @@ -1090,6 +1107,10 @@ func (c *Context) Negotiate(code int, config Negotiate) { data := chooseData(config.YAMLData, config.Data) c.YAML(code, data) + case binding.MIMETOML: + data := chooseData(config.TOMLData, config.Data) + c.TOML(code, data) + default: c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck } diff --git a/context_test.go b/context_test.go index 20038e9..7e4d0b3 100644 --- a/context_test.go +++ b/context_test.go @@ -1773,6 +1773,23 @@ func TestContextShouldBindWithYAML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextShouldBindWithTOML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'")) + c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type + + var obj struct { + Foo string `toml:"foo"` + Bar string `toml:"bar"` + } + assert.NoError(t, c.ShouldBindTOML(&obj)) + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextBadAutoShouldBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) diff --git a/go.mod b/go.mod index 2da1fe3..e519b36 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/goccy/go-json v0.9.7 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 + github.com/pelletier/go-toml/v2 v2.0.0-beta.6 github.com/stretchr/testify v1.7.1 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 diff --git a/go.sum b/go.sum index d7a221f..d7555f9 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.0-beta.6 h1:JFNqj2afbbhCqTiyN16D7Tudc/aaDzE2FBDk+VlBQnE= +github.com/pelletier/go-toml/v2 v2.0.0-beta.6/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -46,8 +48,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= diff --git a/render/render.go b/render/render.go index bcd568b..1fa4806 100644 --- a/render/render.go +++ b/render/render.go @@ -30,6 +30,7 @@ var ( _ Render = Reader{} _ Render = AsciiJSON{} _ Render = ProtoBuf{} + _ Render = TOML{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/toml.go b/render/toml.go new file mode 100644 index 0000000..1192c78 --- /dev/null +++ b/render/toml.go @@ -0,0 +1,32 @@ +package render + +import ( + "net/http" + + "github.com/pelletier/go-toml/v2" +) + +// TOML contains the given interface object. +type TOML struct { + Data any +} + +var TOMLContentType = []string{"application/toml; charset=utf-8"} + +// Render (TOML) marshals the given interface object and writes data with custom ContentType. +func (r TOML) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + + bytes, err := toml.Marshal(r.Data) + if err != nil { + return err + } + + _, err = w.Write(bytes) + return err +} + +// WriteContentType (TOML) writes TOML ContentType for response. +func (r TOML) WriteContentType(w http.ResponseWriter) { + writeContentType(w, TOMLContentType) +} From 4b68a5f12af4d6d2be83e1895f783d5dd5d5a148 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sat, 28 May 2022 10:42:28 +0800 Subject: [PATCH 681/912] chore: update go.mod and remove space from copyright (#3158) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- any.go | 2 +- auth.go | 2 +- auth_test.go | 2 +- benchmarks_test.go | 2 +- binding/any.go | 2 +- binding/binding.go | 2 +- binding/binding_test.go | 2 +- binding/default_validator.go | 2 +- binding/default_validator_benchmark_test.go | 4 ++ binding/form.go | 2 +- binding/form_mapping.go | 2 +- binding/form_mapping_benchmark_test.go | 2 +- binding/header.go | 4 ++ binding/json.go | 2 +- binding/msgpack.go | 2 +- binding/multipart_form_mapping.go | 2 +- binding/multipart_form_mapping_test.go | 2 +- binding/protobuf.go | 2 +- binding/query.go | 2 +- binding/toml.go | 4 ++ binding/validate_test.go | 2 +- binding/xml.go | 2 +- binding/yaml.go | 2 +- context.go | 2 +- context_1.16_test.go | 2 +- context_1.17_test.go | 41 ++++++++++++++++- context_appengine.go | 2 +- context_go17_test.go | 50 --------------------- context_test.go | 2 +- debug.go | 2 +- debug_test.go | 2 +- deprecated.go | 2 +- deprecated_test.go | 2 +- errors.go | 2 +- errors_test.go | 2 +- fs.go | 2 +- gin.go | 2 +- ginS/gins.go | 2 +- gin_integration_test.go | 2 +- gin_test.go | 2 +- githubapi_test.go | 2 +- go.mod | 2 +- go.sum | 6 +-- internal/json/go_json.go | 2 +- internal/json/json.go | 2 +- internal/json/jsoniter.go | 2 +- logger.go | 2 +- logger_test.go | 2 +- middleware_test.go | 2 +- mode.go | 2 +- mode_test.go | 2 +- recovery.go | 2 +- recovery_test.go | 2 +- render/any.go | 2 +- render/data.go | 2 +- render/html.go | 2 +- render/json.go | 2 +- render/msgpack.go | 2 +- render/protobuf.go | 2 +- render/reader.go | 2 +- render/reader_test.go | 2 +- render/redirect.go | 2 +- render/render.go | 2 +- render/render_test.go | 2 +- render/text.go | 2 +- render/toml.go | 4 ++ render/xml.go | 2 +- render/yaml.go | 2 +- response_writer.go | 2 +- response_writer_test.go | 2 +- routergroup.go | 2 +- routergroup_test.go | 2 +- routes_test.go | 2 +- test_helpers.go | 2 +- testdata/protoexample/any.go | 2 +- utils.go | 2 +- utils_test.go | 2 +- version.go | 2 +- 78 files changed, 129 insertions(+), 126 deletions(-) delete mode 100644 context_go17_test.go diff --git a/any.go b/any.go index a0104dc..42b1ea4 100644 --- a/any.go +++ b/any.go @@ -1,4 +1,4 @@ -// Copyright 2022 Gin Core Team. All rights reserved. +// Copyright 2022 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/auth.go b/auth.go index 482c499..2503c51 100644 --- a/auth.go +++ b/auth.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/auth_test.go b/auth_test.go index e44bd10..42b6f8f 100644 --- a/auth_test.go +++ b/auth_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/benchmarks_test.go b/benchmarks_test.go index 0b3f82d..5b7929b 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/any.go b/binding/any.go index 1331d39..d8251a7 100644 --- a/binding/any.go +++ b/binding/any.go @@ -1,4 +1,4 @@ -// Copyright 2022 Gin Core Team. All rights reserved. +// Copyright 2022 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/binding.go b/binding/binding.go index 5051051..a58924e 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/binding_test.go b/binding/binding_test.go index f08e173..f099621 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/default_validator.go b/binding/default_validator.go index 3515a8c..c03afe7 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -1,4 +1,4 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/default_validator_benchmark_test.go b/binding/default_validator_benchmark_test.go index 8d62836..9292e2a 100644 --- a/binding/default_validator_benchmark_test.go +++ b/binding/default_validator_benchmark_test.go @@ -1,3 +1,7 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package binding import ( diff --git a/binding/form.go b/binding/form.go index f5cbf57..b17352b 100644 --- a/binding/form.go +++ b/binding/form.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/form_mapping.go b/binding/form_mapping.go index c24dd55..98cebfe 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/form_mapping_benchmark_test.go b/binding/form_mapping_benchmark_test.go index 9572ea0..5788133 100644 --- a/binding/form_mapping_benchmark_test.go +++ b/binding/form_mapping_benchmark_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 Gin Core Team. All rights reserved. +// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/header.go b/binding/header.go index 14f525e..03bc78d 100644 --- a/binding/header.go +++ b/binding/header.go @@ -1,3 +1,7 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package binding import ( diff --git a/binding/json.go b/binding/json.go index 2e3e1dd..36eb27a 100644 --- a/binding/json.go +++ b/binding/json.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/msgpack.go b/binding/msgpack.go index 6519771..d1f035e 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -1,4 +1,4 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/multipart_form_mapping.go b/binding/multipart_form_mapping.go index c4d7ed7..4ebe832 100644 --- a/binding/multipart_form_mapping.go +++ b/binding/multipart_form_mapping.go @@ -1,4 +1,4 @@ -// Copyright 2019 Gin Core Team. All rights reserved. +// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go index 545914d..9932860 100644 --- a/binding/multipart_form_mapping_test.go +++ b/binding/multipart_form_mapping_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 Gin Core Team. All rights reserved. +// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/protobuf.go b/binding/protobuf.go index ace8f65..44f2fdb 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/query.go b/binding/query.go index 9790ce6..c958b88 100644 --- a/binding/query.go +++ b/binding/query.go @@ -1,4 +1,4 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/toml.go b/binding/toml.go index 5b9ad16..83b2726 100644 --- a/binding/toml.go +++ b/binding/toml.go @@ -1,3 +1,7 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package binding import ( diff --git a/binding/validate_test.go b/binding/validate_test.go index d05c891..801bd9b 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/xml.go b/binding/xml.go index e62af4a..a70f4ad 100644 --- a/binding/xml.go +++ b/binding/xml.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/yaml.go b/binding/yaml.go index 183f141..b0d36a3 100644 --- a/binding/yaml.go +++ b/binding/yaml.go @@ -1,4 +1,4 @@ -// Copyright 2018 Gin Core Team. All rights reserved. +// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/context.go b/context.go index ecbd3da..6b25b3a 100644 --- a/context.go +++ b/context.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/context_1.16_test.go b/context_1.16_test.go index 053e6c5..2676050 100644 --- a/context_1.16_test.go +++ b/context_1.16_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Gin Core Team. All rights reserved. +// Copyright 2021 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/context_1.17_test.go b/context_1.17_test.go index 431d54c..69c9786 100644 --- a/context_1.17_test.go +++ b/context_1.17_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 Gin Core Team. All rights reserved. +// Copyright 2021 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. @@ -17,6 +17,16 @@ import ( "github.com/stretchr/testify/assert" ) +type interceptedWriter struct { + ResponseWriter + b *bytes.Buffer +} + +func (i interceptedWriter) WriteHeader(code int) { + i.Header().Del("X-Test") + i.ResponseWriter.WriteHeader(code) +} + func TestContextFormFileFailed17(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) @@ -31,3 +41,32 @@ func TestContextFormFileFailed17(t *testing.T) { assert.Nil(t, f) }) } + +func TestInterceptedHeader(t *testing.T) { + w := httptest.NewRecorder() + c, r := CreateTestContext(w) + + r.Use(func(c *Context) { + i := interceptedWriter{ + ResponseWriter: c.Writer, + b: bytes.NewBuffer(nil), + } + c.Writer = i + c.Next() + c.Header("X-Test", "overridden") + c.Writer = i.ResponseWriter + }) + r.GET("/", func(c *Context) { + c.Header("X-Test", "original") + c.Header("X-Test-2", "present") + c.String(http.StatusOK, "hello world") + }) + c.Request = httptest.NewRequest("GET", "/", nil) + r.HandleContext(c) + // Result() has headers frozen when WriteHeaderNow() has been called + // Compared to this time, this is when the response headers will be flushed + // As response is flushed on c.String, the Header cannot be set by the first + // middleware. Assert this + assert.Equal(t, "", w.Result().Header.Get("X-Test")) + assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) +} diff --git a/context_appengine.go b/context_appengine.go index 8bf9389..931313f 100644 --- a/context_appengine.go +++ b/context_appengine.go @@ -1,4 +1,4 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/context_go17_test.go b/context_go17_test.go deleted file mode 100644 index eca089c..0000000 --- a/context_go17_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// +build go1.7 - -package gin - -import ( - "bytes" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -type interceptedWriter struct { - ResponseWriter - b *bytes.Buffer -} - -func (i interceptedWriter) WriteHeader(code int) { - i.Header().Del("X-Test") - i.ResponseWriter.WriteHeader(code) -} -func TestInterceptedHeader(t *testing.T) { - w := httptest.NewRecorder() - c, r := CreateTestContext(w) - - r.Use(func(c *Context) { - i := interceptedWriter{ - ResponseWriter: c.Writer, - b: bytes.NewBuffer(nil), - } - c.Writer = i - c.Next() - c.Header("X-Test", "overridden") - c.Writer = i.ResponseWriter - }) - r.GET("/", func(c *Context) { - c.Header("X-Test", "original") - c.Header("X-Test-2", "present") - c.String(http.StatusOK, "hello world") - }) - c.Request = httptest.NewRequest("GET", "/", nil) - r.HandleContext(c) - // Result() has headers frozen when WriteHeaderNow() has been called - // Compared to this time, this is when the response headers will be flushed - // As response is flushed on c.String, the Header cannot be set by the first - // middleware. Assert this - assert.Equal(t, "", w.Result().Header.Get("X-Test")) - assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) -} diff --git a/context_test.go b/context_test.go index 7e4d0b3..f9f0c1d 100644 --- a/context_test.go +++ b/context_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/debug.go b/debug.go index d8d6c8e..25fd7c8 100644 --- a/debug.go +++ b/debug.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/debug_test.go b/debug_test.go index 7c54444..4ac55fe 100644 --- a/debug_test.go +++ b/debug_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/deprecated.go b/deprecated.go index 004e13d..fdad855 100644 --- a/deprecated.go +++ b/deprecated.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/deprecated_test.go b/deprecated_test.go index f8df651..0240b2e 100644 --- a/deprecated_test.go +++ b/deprecated_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/errors.go b/errors.go index 578e857..2853ce8 100644 --- a/errors.go +++ b/errors.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/errors_test.go b/errors_test.go index ac72dc4..78d561c 100644 --- a/errors_test.go +++ b/errors_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/fs.go b/fs.go index e5f3d60..6427473 100644 --- a/fs.go +++ b/fs.go @@ -1,4 +1,4 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/gin.go b/gin.go index 3a831e5..e516360 100644 --- a/gin.go +++ b/gin.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/ginS/gins.go b/ginS/gins.go index 0802e08..1550b86 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/gin_integration_test.go b/gin_integration_test.go index 0dfa903..b0532a2 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -1,4 +1,4 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/gin_test.go b/gin_test.go index a438062..02f2324 100644 --- a/gin_test.go +++ b/gin_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/githubapi_test.go b/githubapi_test.go index 5fe65a4..c6350e8 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/go.mod b/go.mod index e519b36..6cdde34 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/goccy/go-json v0.9.7 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 - github.com/pelletier/go-toml/v2 v2.0.0-beta.6 + github.com/pelletier/go-toml/v2 v2.0.1 github.com/stretchr/testify v1.7.1 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 diff --git a/go.sum b/go.sum index d7555f9..d06b810 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.0-beta.6 h1:JFNqj2afbbhCqTiyN16D7Tudc/aaDzE2FBDk+VlBQnE= -github.com/pelletier/go-toml/v2 v2.0.0-beta.6/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA= +github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -48,10 +48,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= diff --git a/internal/json/go_json.go b/internal/json/go_json.go index da96057..23f7172 100644 --- a/internal/json/go_json.go +++ b/internal/json/go_json.go @@ -1,4 +1,4 @@ -// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Copyright 2017 Bo-Yi Wu. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/internal/json/json.go b/internal/json/json.go index 75b6022..a26d7db 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -1,4 +1,4 @@ -// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Copyright 2017 Bo-Yi Wu. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index 232f8dc..853b1a9 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -1,4 +1,4 @@ -// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Copyright 2017 Bo-Yi Wu. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/logger.go b/logger.go index 1f9d63a..cd1e7fa 100644 --- a/logger.go +++ b/logger.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/logger_test.go b/logger_test.go index b704998..fa0d9ce 100644 --- a/logger_test.go +++ b/logger_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/middleware_test.go b/middleware_test.go index e0a756c..a235fe9 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/mode.go b/mode.go index 4e7d734..545fdaa 100644 --- a/mode.go +++ b/mode.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/mode_test.go b/mode_test.go index 6fd9a13..2407f46 100644 --- a/mode_test.go +++ b/mode_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/recovery.go b/recovery.go index 3efe146..abb6451 100644 --- a/recovery.go +++ b/recovery.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/recovery_test.go b/recovery_test.go index 2c327a6..347917e 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/any.go b/render/any.go index 57349be..b19ad45 100644 --- a/render/any.go +++ b/render/any.go @@ -1,4 +1,4 @@ -// Copyright 2021 Gin Core Team. All rights reserved. +// Copyright 2021 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/data.go b/render/data.go index 6ba657b..a653ea3 100644 --- a/render/data.go +++ b/render/data.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/html.go b/render/html.go index bdfaf11..c308408 100644 --- a/render/html.go +++ b/render/html.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/json.go b/render/json.go index 0a7dcef..af678e8 100644 --- a/render/json.go +++ b/render/json.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/msgpack.go b/render/msgpack.go index 2c0e9aa..e0f30f7 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -1,4 +1,4 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/protobuf.go b/render/protobuf.go index 2e57f5c..9331c40 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -1,4 +1,4 @@ -// Copyright 2018 Gin Core Team. All rights reserved. +// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/reader.go b/render/reader.go index d5282e4..5752d8d 100644 --- a/render/reader.go +++ b/render/reader.go @@ -1,4 +1,4 @@ -// Copyright 2018 Gin Core Team. All rights reserved. +// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/reader_test.go b/render/reader_test.go index 3930f51..aaceb9e 100644 --- a/render/reader_test.go +++ b/render/reader_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 Gin Core Team. All rights reserved. +// Copyright 2019 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/redirect.go b/render/redirect.go index c006691..70e3a47 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/render.go b/render/render.go index 1fa4806..7955000 100644 --- a/render/render.go +++ b/render/render.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/render_test.go b/render/render_test.go index 8b28dc3..a13fff4 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/text.go b/render/text.go index b77a776..77eafdf 100644 --- a/render/text.go +++ b/render/text.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/toml.go b/render/toml.go index 1192c78..40f044c 100644 --- a/render/toml.go +++ b/render/toml.go @@ -1,3 +1,7 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + package render import ( diff --git a/render/xml.go b/render/xml.go index c396a5a..6af8901 100644 --- a/render/xml.go +++ b/render/xml.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/yaml.go b/render/yaml.go index 0fc7a66..4f0ac01 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/response_writer.go b/response_writer.go index 7f9095f..77c7ed8 100644 --- a/response_writer.go +++ b/response_writer.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/response_writer_test.go b/response_writer_test.go index 9061d02..57d163c 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/routergroup.go b/routergroup.go index 3fba3a9..3c082d9 100644 --- a/routergroup.go +++ b/routergroup.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/routergroup_test.go b/routergroup_test.go index c1fad3a..41f9637 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/routes_test.go b/routes_test.go index 5643097..d7034b2 100644 --- a/routes_test.go +++ b/routes_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/test_helpers.go b/test_helpers.go index 3a7a5dd..b3be93b 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -1,4 +1,4 @@ -// Copyright 2017 Manu Martinez-Almeida. All rights reserved. +// Copyright 2017 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/testdata/protoexample/any.go b/testdata/protoexample/any.go index f16864c..2203f33 100644 --- a/testdata/protoexample/any.go +++ b/testdata/protoexample/any.go @@ -1,4 +1,4 @@ -// Copyright 2021 Gin Core Team. All rights reserved. +// Copyright 2021 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/utils.go b/utils.go index b41a3b0..4021a2a 100644 --- a/utils.go +++ b/utils.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/utils_test.go b/utils_test.go index d2a740b..058ddb9 100644 --- a/utils_test.go +++ b/utils_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/version.go b/version.go index 4b69b9b..40e6505 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ -// Copyright 2018 Gin Core Team. All rights reserved. +// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. From 60e24d5690296e583c1ad67579b1f016da76df3d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 28 May 2022 15:23:00 +0800 Subject: [PATCH 682/912] chore(CI/CD): add go version release flow (#3159) * chore(CI/CD): add go version release flow Signed-off-by: Bo-Yi Wu * chore: bump to v1.8.0 Signed-off-by: Bo-Yi Wu * chore: update Signed-off-by: Bo-Yi Wu --- .github/workflows/goreleaser.yml | 34 +++++++++++++++++++ .goreleaser.yaml | 57 ++++++++++++++++++++++++++++++++ version.go | 2 +- 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/goreleaser.yml create mode 100644 .goreleaser.yaml diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml new file mode 100644 index 0000000..e0f6e4f --- /dev/null +++ b/.github/workflows/goreleaser.yml @@ -0,0 +1,34 @@ +name: Goreleaser + +on: + push: + tags: + - '*' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.17 + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + # either 'goreleaser' (default) or 'goreleaser-pro' + distribution: goreleaser + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..e435e56 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,57 @@ +project_name: gin + +builds: + - + # If true, skip the build. + # Useful for library projects. + # Default is false + skip: true + +changelog: + # Set it to true if you wish to skip the changelog generation. + # This may result in an empty release notes on GitHub/GitLab/Gitea. + skip: false + + # Changelog generation implementation to use. + # + # Valid options are: + # - `git`: uses `git log`; + # - `github`: uses the compare GitHub API, appending the author login to the changelog. + # - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog. + # - `github-native`: uses the GitHub release notes generation API, disables the groups feature. + # + # Defaults to `git`. + use: git + + # Sorts the changelog by the commit's messages. + # Could either be asc, desc or empty + # Default is empty + sort: asc + + # Group commits messages by given regex and title. + # Order value defines the order of the groups. + # Proving no regex means all commits will be grouped under the default group. + # Groups are disabled when using github-native, as it already groups things by itself. + # + # Default is no groups. + groups: + - title: Features + regexp: "^.*feat[(\\w)]*:+.*$" + order: 0 + - title: 'Bug fixes' + regexp: "^.*fix[(\\w)]*:+.*$" + order: 1 + - title: 'Enhancements' + regexp: "^.*chore[(\\w)]*:+.*$" + order: 2 + - title: Others + order: 999 + + filters: + # Commit messages matching the regexp listed here will be removed from + # the changelog + # Default is empty + exclude: + - '^docs' + - 'CICD' + - typo diff --git a/version.go b/version.go index 40e6505..9b0eae2 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.7.7" +const Version = "v1.8.0" From 38eb5acc6b07eea5bf455e8d188bf79fa897c7c3 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 30 May 2022 15:16:10 +0800 Subject: [PATCH 683/912] add v1.8.0 changelog (#3160) --- AUTHORS.md | 622 +++++++++++++++++++++------------- CHANGELOG.md | 38 +++ binding/toml.go | 2 +- binding/uri.go | 2 +- render/render_msgpack_test.go | 2 +- 5 files changed, 436 insertions(+), 230 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 533204e..b4773ef 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -2,237 +2,405 @@ List of all the awesome people working to make Gin the best Web Framework in Go. ## gin 1.x series authors -**Gin Core Team:** Bo-Yi Wu (@appleboy), 田欧 (@thinkerou), Javier Provecho (@javierprovecho) +**Gin Core Team:** Bo-Yi Wu (@appleboy), thinkerou (@thinkerou), Javier Provecho (@javierprovecho) ## gin 0.x series authors **Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho) -People and companies, who have contributed, in alphabetical order. - -**@858806258 (杰哥)** -- Fix typo in example - - -**@achedeuzot (Klemen Sever)** -- Fix newline debug printing - - -**@adammck (Adam Mckaig)** -- Add MIT license - - -**@AlexanderChen1989 (Alexander)** -- Typos in README - - -**@alexanderdidenko (Aleksandr Didenko)** -- Add support multipart/form-data - - -**@alexandernyquist (Alexander Nyquist)** -- Using template.Must to fix multiple return issue -- ★ Added support for OPTIONS verb -- ★ Setting response headers before calling WriteHeader -- Improved documentation for model binding -- ★ Added Content.Redirect() -- ★ Added tons of Unit tests - - -**@austinheap (Austin Heap)** -- Added travis CI integration - - -**@andredublin (Andre Dublin)** -- Fix typo in comment - - -**@bredov (Ludwig Valda Vasquez)** -- Fix html templating in debug mode - - -**@bluele (Jun Kimura)** -- Fixes code examples in README - - -**@chad-russell** -- ★ Support for serializing gin.H into XML - - -**@dickeyxxx (Jeff Dickey)** -- Typos in README -- Add example about serving static files - - -**@donileo (Adonis)** -- Add NoMethod handler - - -**@dutchcoders (DutchCoders)** -- ★ Fix security bug that allows client to spoof ip -- Fix typo. r.HTMLTemplates -> SetHTMLTemplate - - -**@el3ctro- (Joshua Loper)** -- Fix typo in example - - -**@ethankan (Ethan Kan)** -- Unsigned integers in binding - - -**(Evgeny Persienko)** -- Validate sub structures - - -**@frankbille (Frank Bille)** -- Add support for HTTP Realm Auth - - -**@fmd (Fareed Dudhia)** -- Fix typo. SetHTTPTemplate -> SetHTMLTemplate - - -**@ironiridis (Christopher Harrington)** -- Remove old reference - - -**@jammie-stackhouse (Jamie Stackhouse)** -- Add more shortcuts for router methods - - -**@jasonrhansen** -- Fix spelling and grammar errors in documentation - - -**@JasonSoft (Jason Lee)** -- Fix typo in comment - - -**@jincheng9 (Jincheng Zhang)** -- ★ support TSR when wildcard follows named param -- Fix errors and typos in comments - - -**@joiggama (Ignacio Galindo)** -- Add utf-8 charset header on renders - - -**@julienschmidt (Julien Schmidt)** -- gofmt the code examples - - -**@kelcecil (Kel Cecil)** -- Fix readme typo - - -**@kyledinh (Kyle Dinh)** -- Adds RunTLS() - - -**@LinusU (Linus Unnebäck)** -- Small fixes in README - - -**@loongmxbt (Saint Asky)** -- Fix typo in example - - -**@lucas-clemente (Lucas Clemente)** -- ★ work around path.Join removing trailing slashes from routes - - -**@mattn (Yasuhiro Matsumoto)** -- Improve color logger - - -**@mdigger (Dmitry Sedykh)** -- Fixes Form binding when content-type is x-www-form-urlencoded -- No repeat call c.Writer.Status() in gin.Logger -- Fixes Content-Type for json render - - -**@mirzac (Mirza Ceric)** -- Fix debug printing - - -**@mopemope (Yutaka Matsubara)** -- ★ Adds Godep support (Dependencies Manager) -- Fix variadic parameter in the flexible render API -- Fix Corrupted plain render -- Add Pluggable View Renderer Example - - -**@msemenistyi (Mykyta Semenistyi)** -- update Readme.md. Add code to String method - - -**@msoedov (Sasha Myasoedov)** -- ★ Adds tons of unit tests. - - -**@ngerakines (Nick Gerakines)** -- ★ Improves API, c.GET() doesn't panic -- Adds MustGet() method - - -**@r8k (Rajiv Kilaparti)** -- Fix Port usage in README. - - -**@rayrod2030 (Ray Rodriguez)** -- Fix typo in example - - -**@rns** -- Fix typo in example - - -**@RobAWilkinson (Robert Wilkinson)** -- Add example of forms and params - - -**@rogierlommers (Rogier Lommers)** -- Add updated static serve example - -**@rw-access (Ross Wolf)** -- Added support to mix exact and param routes - -**@se77en (Damon Zhao)** -- Improve color logging - - -**@silasb (Silas Baronda)** -- Fixing quotes in README - - -**@SkuliOskarsson (Skuli Oskarsson)** -- Fixes some texts in README II - - -**@slimmy (Jimmy Pettersson)** -- Added messages for required bindings - - -**@smira (Andrey Smirnov)** -- Add support for ignored/unexported fields in binding - - -**@superalsrk (SRK.Lyu)** -- Update httprouter godeps - - -**@tebeka (Miki Tebeka)** -- Use net/http constants instead of numeric values - - -**@techjanitor** -- Update context.go reserved IPs - - -**@yosssi (Keiji Yoshida)** -- Fix link in README +------ +People and companies, who have contributed, in alphabetical order. -**@yuyabee** -- Fixed README +- 178inaba <178inaba@users.noreply.github.com> +- A. F +- ABHISHEK SONI +- Abhishek Chanda +- Abner Chen +- AcoNCodes +- Adam Dratwinski +- Adam Mckaig +- Adam Zielinski +- Adonis +- Alan Wang +- Albin Gilles +- Aleksandr Didenko +- Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com> +- Alex +- Alexander +- Alexander Lokhman +- Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com> +- Alexander Nyquist +- Allen Ren +- AllinGo +- Ammar Bandukwala +- An Xiao (Luffy) +- Andre Dublin <81dublin@gmail.com> +- Andrew Szeto +- Andrey Abramov +- Andrey Nering +- Andrey Smirnov +- Andrii Bubis +- André Bazaglia +- Andy Pan +- Antoine GIRARD +- Anup Kumar Panwar <1anuppanwar@gmail.com> +- Aravinth Sundaram +- Artem +- Ashwani +- Aurelien Regat-Barrel +- Austin Heap +- Barnabus +- Bo-Yi Wu +- Boris Borshevsky +- Boyi Wu +- BradyBromley <51128276+BradyBromley@users.noreply.github.com> +- Brendan Fosberry +- Brian Wigginton +- Carlos Eduardo +- Chad Russell +- Charles +- Christian Muehlhaeuser +- Christian Persson +- Christopher Harrington +- Damon Zhao +- Dan Markham +- Dang Nguyen +- Daniel Krom +- Daniel M. Lambea +- Danieliu +- David Irvine +- David Zhang +- Davor Kapsa +- DeathKing +- Dennis Cho <47404603+forest747@users.noreply.github.com> +- Dmitry Dorogin +- Dmitry Kutakov +- Dmitry Sedykh +- Don2Quixote <35610661+Don2Quixote@users.noreply.github.com> +- Donn Pebe +- Dustin Decker +- Eason Lin +- Edward Betts +- Egor Seredin <4819888+agmt@users.noreply.github.com> +- Emmanuel Goh +- Equim +- Eren A. Akyol +- Eric_Lee +- Erik Bender +- Ethan Kan +- Evgeny Persienko +- Faisal Alam +- Fareed Dudhia +- Filip Figiel +- Florian Polster +- Frank Bille +- Franz Bettag +- Ganlv +- Gaozhen Ying +- George Gabolaev +- George Kirilenko +- Georges Varouchas +- Gordon Tyler +- Harindu Perera +- Helios <674876158@qq.com> +- Henry Kwan +- Henry Yee +- Himanshu Mishra +- Hiroyuki Tanaka +- Ibraheem Ahmed +- Ignacio Galindo +- Igor H. Vieira +- Ildar1111 <54001462+Ildar1111@users.noreply.github.com> +- Iskander (Alex) Sharipov +- Ismail Gjevori +- Ivan Chen +- JINNOUCHI Yasushi +- James Pettyjohn +- Jamie Stackhouse +- Jason Lee +- Javier Provecho +- Javier Provecho +- Javier Provecho +- Javier Provecho Fernandez +- Javier Provecho Fernandez +- Jean-Christophe Lebreton +- Jeff +- Jeremy Loy +- Jim Filippou +- Jimmy Pettersson +- John Bampton +- Johnny Dallas +- Johnny Dallas +- Jonathan (JC) Chen +- Josep Jesus Bigorra Algaba <42377845+averageflow@users.noreply.github.com> +- Josh Horowitz +- Joshua Loper +- Julien Schmidt +- Jun Kimura +- Justin Beckwith +- Justin Israel +- Justin Mayhew +- Jérôme Laforge +- Kacper Bąk <56700396+53jk1@users.noreply.github.com> +- Kamron Batman +- Kane Rogers +- Kaushik Neelichetty +- Keiji Yoshida +- Kel Cecil +- Kevin Mulvey +- Kevin Zhu +- Kirill Motkov +- Klemen Sever +- Kristoffer A. Iversen +- Krzysztof Szafrański +- Kumar McMillan +- Kyle Mcgill +- Lanco <35420416+lancoLiu@users.noreply.github.com> +- Levi Olson +- Lin Kao-Yuan +- Linus Unnebäck +- Lucas Clemente +- Ludwig Valda Vasquez +- Luis GG +- MW Lim +- Maksimov Sergey +- Manjusaka +- Manu MA +- Manu MA +- Manu Mtz-Almeida +- Manu Mtz.-Almeida +- Manuel Alonso +- Mara Kim +- Mario Kostelac +- Martin Karlsch +- Matt Newberry +- Matt Williams +- Matthieu MOREL +- Max Hilbrunner +- Maxime Soulé +- MetalBreaker +- Michael Puncel +- MichaelDeSteven <51652084+MichaelDeSteven@users.noreply.github.com> +- Mike <38686456+icy4ever@users.noreply.github.com> +- Mike Stipicevic +- Miki Tebeka +- Miles +- Mirza Ceric +- Mykyta Semenistyi +- Naoki Takano +- Ngalim Siregar +- Ni Hao +- Nick Gerakines +- Nikifor Seryakov +- Notealot <714804968@qq.com> +- Olivier Mengué +- Olivier Robardet +- Pablo Moncada +- Pablo Moncada +- Panmax <967168@qq.com> +- Peperoncino <2wua4nlyi@gmail.com> +- Philipp Meinen +- Pierre Massat +- Qt +- Quentin ROYER +- README Bot <35302948+codetriage-readme-bot@users.noreply.github.com> +- Rafal Zajac +- Rahul Datta Roy +- Rajiv Kilaparti +- Raphael Gavache +- Ray Rodriguez +- Regner Blok-Andersen +- Remco +- Rex Lee(李俊) +- Richard Lee +- Riverside +- Robert Wilkinson +- Rogier Lommers +- Rohan Pai +- Romain Beuque +- Roman Belyakovsky +- Roman Zaynetdinov <627197+zaynetro@users.noreply.github.com> +- Roman Zaynetdinov +- Ronald Petty +- Ross Wolf <31489089+rw-access@users.noreply.github.com> +- Roy Lou +- Rubi <14269809+codenoid@users.noreply.github.com> +- Ryan <46182144+ryanker@users.noreply.github.com> +- Ryan J. Yoder +- SRK.Lyu +- Sai +- Samuel Abreu +- Santhosh Kumar +- Sasha Melentyev +- Sasha Myasoedov +- Segev Finer +- Sergey Egorov +- Sergey Fedchenko +- Sergey Gonimar +- Sergey Ponomarev +- Serica <943914044@qq.com> +- Shamus Taylor +- Shilin Wang +- Shuo +- Skuli Oskarsson +- Snawoot +- Sridhar Ratnakumar +- Steeve Chailloux +- Sudhir Mishra +- Suhas Karanth +- TaeJun Park +- Tatsuya Hoshino +- Tevic +- Tevin Jeffrey +- The Gitter Badger +- Thibault Jamet +- Thomas Boerger +- Thomas Schaffer +- Tommy Chu +- Tudor Roman +- Uwe Dauernheim +- Valentine Oragbakosi +- Vas N +- Vasilyuk Vasiliy +- Victor Castell +- Vince Yuan +- Vyacheslav Dubinin +- Waynerv +- Weilin Shi <934587911@qq.com> +- Xudong Cai +- Yasuhiro Matsumoto +- Yehezkiel Syamsuhadi +- Yoshiki Nakagawa +- Yoshiyuki Kinjo +- Yue Yang +- ZYunH +- Zach Newburgh +- Zasda Yusuf Mikail +- ZhangYunHao +- ZhiFeng Hu +- Zhu Xi +- a2tt +- ahuigo <1781999+ahuigo@users.noreply.github.com> +- ali +- aljun +- andrea +- andriikushch +- anoty +- awkj +- axiaoxin <254606826@qq.com> +- bbiao +- bestgopher <84328409@qq.com> +- betahu +- bigwheel +- bn4t <17193640+bn4t@users.noreply.github.com> +- bullgare +- chainhelen +- chenyang929 +- chriswhelix +- collinmsn <4130944@qq.com> +- cssivision +- danielalves +- delphinus +- dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +- dickeyxxx +- edebernis +- error10 +- esplo +- eudore <30709860+eudore@users.noreply.github.com> +- ffhelicopter <32922889+ffhelicopter@users.noreply.github.com> +- filikos <11477309+filikos@users.noreply.github.com> +- forging2012 +- goqihoo +- grapeVine +- guonaihong +- heige +- heige +- hellojukay +- henrylee2cn +- htobenothing +- iamhesir <78344375+iamhesir@users.noreply.github.com> +- ijaa +- ishanray +- ishanray +- itcloudy <272685110@qq.com> +- jarodsong6 +- jasonrhansen +- jincheng9 +- joeADSP <75027008+joeADSP@users.noreply.github.com> +- junfengye +- kaiiak +- kebo +- keke <19yamashita15@gmail.com> +- kishor kunal raj <68464660+kishorkunal-raj@users.noreply.github.com> +- kyledinh +- lantw44 +- likakuli <1154584512@qq.com> +- linfangrong +- linzi <873804682@qq.com> +- llgoer +- long-road <13412081338@163.com> +- mbesancon +- mehdy +- metal A-wing +- micanzhang +- minarc +- mllu +- mopemoepe +- msoedov +- mstmdev +- novaeye +- olebedev +- phithon +- pjgg +- qm012 <67568757+qm012@users.noreply.github.com> +- raymonder jin +- rns +- root@andrea:~# +- sekky0905 <20237968+sekky0905@users.noreply.github.com> +- senhtry +- shadrus +- silasb +- solos +- songjiayang +- sope +- srt180 <30768686+srt180@users.noreply.github.com> +- stackerzzq +- sunshineplan +- syssam +- techjanitor +- techjanitor +- thinkerou +- thinkgo <49174849+thinkgos@users.noreply.github.com> +- tsirolnik +- tyltr <31768692+tylitianrui@users.noreply.github.com> +- vinhha96 +- voidman +- vz +- wei +- weibaohui +- whirosan +- willnewrelic +- wssccc +- wuhuizuo +- xyb +- y-yagi +- yiranzai +- youzeliang +- yugu +- yuyabe +- zebozhuang +- zero11-0203 <93071220+zero11-0203@users.noreply.github.com> +- zesani <7sin@outlook.co.th> +- zhanweidu +- zhing +- ziheng +- zzjin +- 森 優太 <59682979+uta-mori@users.noreply.github.com> +- 杰哥 <858806258@qq.com> +- 涛叔 +- 市民233 +- 尹宝强 +- 梦溪笔谈 +- 飞雪无情 +- 寻寻觅觅的Gopher diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c806a5..6d54028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Gin ChangeLog +## Gin v1.8.0 + +### BUGFIXES + +* Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861) +* Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983) +* Fix: missing sameSite when do context.reset() [#3123](https://github.com/gin-gonic/gin/pull/3123) + +### ENHANCEMENTS + +* Use Header() instead of deprecated HeaderMap [#2694](https://github.com/gin-gonic/gin/pull/2694) +* RouterGroup.Handle regular match optimization of http method [#2685](https://github.com/gin-gonic/gin/pull/2685) +* Add support go-json, another drop-in json replacement [#2680](https://github.com/gin-gonic/gin/pull/2680) +* Use errors.New to replace fmt.Errorf will much better [#2707](https://github.com/gin-gonic/gin/pull/2707) +* Use Duration.Truncate for truncating precision [#2711](https://github.com/gin-gonic/gin/pull/2711) +* Get client IP when using Cloudflare [#2723](https://github.com/gin-gonic/gin/pull/2723) +* Optimize code adjust [#2700](https://github.com/gin-gonic/gin/pull/2700/files) +* Optimize code and reduce code cyclomatic complexity [#2737](https://github.com/gin-gonic/gin/pull/2737) +* gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751) +* Improve sliceValidateError.Error performance [#2765](https://github.com/gin-gonic/gin/pull/2765) +* Support custom struct tag [#2720](https://github.com/gin-gonic/gin/pull/2720) +* Improve router group tests [#2787](https://github.com/gin-gonic/gin/pull/2787) +* Fallback Context.Deadline() Context.Done() Context.Err() to Context.Request.Context() [#2769](https://github.com/gin-gonic/gin/pull/2769) +* Some codes optimize [#2830](https://github.com/gin-gonic/gin/pull/2830) [#2834](https://github.com/gin-gonic/gin/pull/2834) [#2838](https://github.com/gin-gonic/gin/pull/2838) [#2837](https://github.com/gin-gonic/gin/pull/2837) [#2788](https://github.com/gin-gonic/gin/pull/2788) [#2848](https://github.com/gin-gonic/gin/pull/2848) [#2851](https://github.com/gin-gonic/gin/pull/2851) [#2701](https://github.com/gin-gonic/gin/pull/2701) +* TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967) +* Test(route): expose performRequest func [#3012](https://github.com/gin-gonic/gin/pull/3012) +* Support h2c with prior knowledge [#1398](https://github.com/gin-gonic/gin/pull/1398) +* Feat attachment filename support utf8 [#3071](https://github.com/gin-gonic/gin/pull/3071) +* Feat: add StaticFileFS [#2749](https://github.com/gin-gonic/gin/pull/2749) +* Feat(context): return GIN Context from Value method [#2825](https://github.com/gin-gonic/gin/pull/2825) +* Feat: automatically SetMode to TestMode when run go test [#3139](https://github.com/gin-gonic/gin/pull/3139) +* Add TOML bining for gin [#3081](https://github.com/gin-gonic/gin/pull/3081) +* IPv6 add default trusted proxies [#3033](https://github.com/gin-gonic/gin/pull/3033) + +### DOCS + +* Add note about nomsgpack tag to the readme [#2703](https://github.com/gin-gonic/gin/pull/2703) + ## Gin v1.7.7 ### BUGFIXES diff --git a/binding/toml.go b/binding/toml.go index 83b2726..a6b8a90 100644 --- a/binding/toml.go +++ b/binding/toml.go @@ -1,4 +1,4 @@ -// Copyright 2022 Gin Core Team. All rights reserved. +// Copyright 2022 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/binding/uri.go b/binding/uri.go index 4d44ab9..2915106 100644 --- a/binding/uri.go +++ b/binding/uri.go @@ -1,4 +1,4 @@ -// Copyright 2018 Gin Core Team. All rights reserved. +// Copyright 2018 Gin Core Team. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index 7b3601a..6421236 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -1,4 +1,4 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. From 5fa34529aec2d81f5b2b287edfa395e2e8c8904b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 May 2022 07:25:47 +0800 Subject: [PATCH 684/912] chore(deps): bump goreleaser/goreleaser-action from 2 to 3 (#3163) Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 2 to 3. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v2...v3) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index e0f6e4f..64ed8b2 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -24,7 +24,7 @@ jobs: go-version: 1.17 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@v3 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser From 58303bde7da71ba6252190433b112a138e201477 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 2 Jun 2022 09:48:35 +0800 Subject: [PATCH 685/912] docs(changelog): add break changes section (#3170) * docs(changelog): add break changes section * chore: update --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d54028..1e7e83e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Gin v1.8.0 +## Break Changes + +* TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967). Please replace `RemoteIP() (net.IP, bool)` with `RemoteIP() net.IP` +* gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751) + ### BUGFIXES * Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861) @@ -18,7 +23,6 @@ * Get client IP when using Cloudflare [#2723](https://github.com/gin-gonic/gin/pull/2723) * Optimize code adjust [#2700](https://github.com/gin-gonic/gin/pull/2700/files) * Optimize code and reduce code cyclomatic complexity [#2737](https://github.com/gin-gonic/gin/pull/2737) -* gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751) * Improve sliceValidateError.Error performance [#2765](https://github.com/gin-gonic/gin/pull/2765) * Support custom struct tag [#2720](https://github.com/gin-gonic/gin/pull/2720) * Improve router group tests [#2787](https://github.com/gin-gonic/gin/pull/2787) From 92ba8e17aae5103b98cd6b87a300b33d31716a87 Mon Sep 17 00:00:00 2001 From: Qt Date: Thu, 2 Jun 2022 11:52:28 +0800 Subject: [PATCH 686/912] fix: typo (#3171) --- README.md | 75 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 6b4cabb..5cc8321 100644 --- a/README.md +++ b/README.md @@ -114,12 +114,16 @@ $ cat example.go ```go package main -import "github.com/gin-gonic/gin" +import ( + "net/http" + + "github.com/gin-gonic/gin" +) func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { - c.JSON(200, gin.H{ + c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) @@ -300,7 +304,7 @@ func main() { message := c.PostForm("message") nick := c.DefaultPostForm("nick", "anonymous") - c.JSON(200, gin.H{ + c.JSON(http.StatusOK, gin.H{ "status": "posted", "message": message, "nick": nick, @@ -570,7 +574,7 @@ func main() { router := gin.Default() router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") })    router.Run(":8080") @@ -602,7 +606,7 @@ func main() { router.Use(gin.Recovery()) router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) router.Run(":8080") @@ -630,7 +634,7 @@ func main() { router := gin.Default() router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) router.Run(":8080") @@ -649,7 +653,7 @@ func main() { router := gin.Default() router.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) router.Run(":8080") @@ -848,6 +852,7 @@ package main import ( "log" + "net/http" "github.com/gin-gonic/gin" ) @@ -870,7 +875,7 @@ func startPage(c *gin.Context) { log.Println(person.Name) log.Println(person.Address) } - c.String(200, "Success") + c.String(http.StatusOK, "Success") } ``` @@ -884,6 +889,7 @@ package main import ( "log" + "net/http" "time" "github.com/gin-gonic/gin" @@ -916,7 +922,7 @@ func startPage(c *gin.Context) { log.Println(person.UnixTime) } - c.String(200, "Success") + c.String(http.StatusOK, "Success") } ``` @@ -932,7 +938,11 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/846). ```go package main -import "github.com/gin-gonic/gin" +import ( + "net/http" + + "github.com/gin-gonic/gin" +) type Person struct { ID string `uri:"id" binding:"required,uuid"` @@ -944,10 +954,10 @@ func main() { route.GET("/:name/:id", func(c *gin.Context) { var person Person if err := c.ShouldBindUri(&person); err != nil { - c.JSON(400, gin.H{"msg": err.Error()}) + c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) return } - c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) + c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) }) route.Run(":8088") } @@ -966,6 +976,8 @@ package main import ( "fmt" + "net/http" + "github.com/gin-gonic/gin" ) @@ -980,11 +992,11 @@ func main() { h := testHeader{} if err := c.ShouldBindHeader(&h); err != nil { - c.JSON(200, err) + c.JSON(http.StatusOK, err) } fmt.Printf("%#v\n", h) - c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain}) + c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain}) }) r.Run() @@ -1014,7 +1026,7 @@ type myForm struct { func formHandler(c *gin.Context) { var fakeForm myForm c.ShouldBind(&fakeForm) - c.JSON(200, gin.H{"color": fakeForm.Colors}) + c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors}) } ... @@ -1219,14 +1231,14 @@ func main() { // Serves unicode entities r.GET("/json", func(c *gin.Context) { - c.JSON(200, gin.H{ + c.JSON(http.StatusOK, gin.H{ "html": "Hello, world!", }) }) // Serves literal characters r.GET("/purejson", func(c *gin.Context) { - c.PureJSON(200, gin.H{ + c.PureJSON(http.StatusOK, gin.H{ "html": "Hello, world!", }) }) @@ -1473,7 +1485,7 @@ r.GET("/test", func(c *gin.Context) { r.HandleContext(c) }) r.GET("/test2", func(c *gin.Context) { - c.JSON(200, gin.H{"hello": "world"}) + c.JSON(http.StatusOK, gin.H{"hello": "world"}) }) ``` @@ -1626,6 +1638,7 @@ package main import ( "log" + "net/http" "github.com/gin-gonic/autotls" "github.com/gin-gonic/gin" @@ -1636,7 +1649,7 @@ func main() { // Ping handler r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) log.Fatal(autotls.Run(r, "example1.com", "example2.com")) @@ -1650,6 +1663,7 @@ package main import ( "log" + "net/http" "github.com/gin-gonic/autotls" "github.com/gin-gonic/gin" @@ -1661,7 +1675,7 @@ func main() { // Ping handler r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) m := autocert.Manager{ @@ -1922,7 +1936,7 @@ type StructD struct { func GetDataB(c *gin.Context) { var b StructB c.Bind(&b) - c.JSON(200, gin.H{ + c.JSON(http.StatusOK, gin.H{ "a": b.NestedStruct, "b": b.FieldB, }) @@ -1931,7 +1945,7 @@ func GetDataB(c *gin.Context) { func GetDataC(c *gin.Context) { var b StructC c.Bind(&b) - c.JSON(200, gin.H{ + c.JSON(http.StatusOK, gin.H{ "a": b.NestedStructPointer, "c": b.FieldC, }) @@ -1940,7 +1954,7 @@ func GetDataC(c *gin.Context) { func GetDataD(c *gin.Context) { var b StructD c.Bind(&b) - c.JSON(200, gin.H{ + c.JSON(http.StatusOK, gin.H{ "x": b.NestedAnonyStruct, "d": b.FieldD, }) @@ -2090,6 +2104,7 @@ package main import ( "html/template" "log" + "net/http" "github.com/gin-gonic/gin" ) @@ -2118,7 +2133,7 @@ func main() { log.Printf("Failed to push: %v", err) } } - c.HTML(200, "https", gin.H{ + c.HTML(http.StatusOK, "https", gin.H{ "status": "success", }) }) @@ -2274,10 +2289,16 @@ The `net/http/httptest` package is preferable way for HTTP testing. ```go package main +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + func setupRouter() *gin.Engine { r := gin.Default() r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) return r } @@ -2305,10 +2326,10 @@ func TestPingRoute(t *testing.T) { router := setupRouter() w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/ping", nil) + req, _ := http.NewRequest(http.MethodGet, "/ping", nil) router.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "pong", w.Body.String()) } ``` From f197a8bae0c87e1b7cc2e32e399a40665a82f077 Mon Sep 17 00:00:00 2001 From: wei Date: Mon, 6 Jun 2022 18:43:53 +0800 Subject: [PATCH 687/912] feat(context): add ContextWithFallback feature flag (#3166) (#3172) Enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() --- context.go | 8 ++-- context_test.go | 115 +++++++++++++++++++++++++++++++++++++++++++----- gin.go | 3 ++ 3 files changed, 110 insertions(+), 16 deletions(-) diff --git a/context.go b/context.go index 6b25b3a..b1ad95e 100644 --- a/context.go +++ b/context.go @@ -1158,7 +1158,7 @@ func (c *Context) SetAccepted(formats ...string) { // Deadline returns that there is no deadline (ok==false) when c.Request has no Context. func (c *Context) Deadline() (deadline time.Time, ok bool) { - if c.Request == nil || c.Request.Context() == nil { + if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { return } return c.Request.Context().Deadline() @@ -1166,7 +1166,7 @@ func (c *Context) Deadline() (deadline time.Time, ok bool) { // Done returns nil (chan which will wait forever) when c.Request has no Context. func (c *Context) Done() <-chan struct{} { - if c.Request == nil || c.Request.Context() == nil { + if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { return nil } return c.Request.Context().Done() @@ -1174,7 +1174,7 @@ func (c *Context) Done() <-chan struct{} { // Err returns nil when c.Request has no Context. func (c *Context) Err() error { - if c.Request == nil || c.Request.Context() == nil { + if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { return nil } return c.Request.Context().Err() @@ -1195,7 +1195,7 @@ func (c *Context) Value(key any) any { return val } } - if c.Request == nil || c.Request.Context() == nil { + if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { return nil } return c.Request.Context().Value(key) diff --git a/context_test.go b/context_test.go index f9f0c1d..6c0be54 100644 --- a/context_test.go +++ b/context_test.go @@ -2097,12 +2097,18 @@ func TestRemoteIPFail(t *testing.T) { } func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) { - c := &Context{} + c, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c.engine.ContextWithFallback = true + deadline, ok := c.Deadline() assert.Zero(t, deadline) assert.False(t, ok) - c2 := &Context{} + c2, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c2.engine.ContextWithFallback = true + c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil) d := time.Now().Add(time.Second) ctx, cancel := context.WithDeadline(context.Background(), d) @@ -2114,10 +2120,16 @@ func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) { } func TestContextWithFallbackDoneFromRequestContext(t *testing.T) { - c := &Context{} + c, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c.engine.ContextWithFallback = true + assert.Nil(t, c.Done()) - c2 := &Context{} + c2, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c2.engine.ContextWithFallback = true + c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil) ctx, cancel := context.WithCancel(context.Background()) c2.Request = c2.Request.WithContext(ctx) @@ -2126,10 +2138,16 @@ func TestContextWithFallbackDoneFromRequestContext(t *testing.T) { } func TestContextWithFallbackErrFromRequestContext(t *testing.T) { - c := &Context{} + c, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c.engine.ContextWithFallback = true + assert.Nil(t, c.Err()) - c2 := &Context{} + c2, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c2.engine.ContextWithFallback = true + c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil) ctx, cancel := context.WithCancel(context.Background()) c2.Request = c2.Request.WithContext(ctx) @@ -2138,9 +2156,9 @@ func TestContextWithFallbackErrFromRequestContext(t *testing.T) { assert.EqualError(t, c2.Err(), context.Canceled.Error()) } -type contextKey string - func TestContextWithFallbackValueFromRequestContext(t *testing.T) { + type contextKey string + tests := []struct { name string getContextAndKey func() (*Context, any) @@ -2150,7 +2168,9 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { name: "c with struct context key", getContextAndKey: func() (*Context, any) { var key struct{} - c := &Context{} + c, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c.engine.ContextWithFallback = true c.Request, _ = http.NewRequest("POST", "/", nil) c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value")) return c, key @@ -2160,7 +2180,9 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { { name: "c with string context key", getContextAndKey: func() (*Context, any) { - c := &Context{} + c, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c.engine.ContextWithFallback = true c.Request, _ = http.NewRequest("POST", "/", nil) c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value")) return c, contextKey("key") @@ -2170,7 +2192,10 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { { name: "c with nil http.Request", getContextAndKey: func() (*Context, any) { - c := &Context{} + c, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c.engine.ContextWithFallback = true + c.Request = nil return c, "key" }, value: nil, @@ -2178,7 +2203,9 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { { name: "c with nil http.Request.Context()", getContextAndKey: func() (*Context, any) { - c := &Context{} + c, _ := CreateTestContext(httptest.NewRecorder()) + // enable ContextWithFallback feature flag + c.engine.ContextWithFallback = true c.Request, _ = http.NewRequest("POST", "/", nil) return c, "key" }, @@ -2193,6 +2220,70 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { } } +func TestContextCopyShouldNotCancel(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer srv.Close() + + ensureRequestIsOver := make(chan struct{}) + + wg := &sync.WaitGroup{} + + r := New() + r.GET("/", func(ginctx *Context) { + wg.Add(1) + + ginctx = ginctx.Copy() + + // start async goroutine for calling srv + go func() { + defer wg.Done() + + <-ensureRequestIsOver // ensure request is done + + req, err := http.NewRequestWithContext(ginctx, http.MethodGet, srv.URL, nil) + must(err) + + res, err := http.DefaultClient.Do(req) + if err != nil { + t.Error(fmt.Errorf("request error: %w", err)) + return + } + + if res.StatusCode != http.StatusOK { + t.Error(fmt.Errorf("unexpected status code: %s", res.Status)) + } + }() + }) + + l, err := net.Listen("tcp", ":0") + must(err) + go func() { + s := &http.Server{ + Handler: r, + } + + must(s.Serve(l)) + }() + + addr := strings.Split(l.Addr().String(), ":") + res, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/", addr[len(addr)-1])) + if err != nil { + t.Error(fmt.Errorf("request error: %w", err)) + return + } + + close(ensureRequestIsOver) + + if res.StatusCode != http.StatusOK { + t.Error(fmt.Errorf("unexpected status code: %s", res.Status)) + return + } + + wg.Wait() +} + func TestContextAddParam(t *testing.T) { c := &Context{} id := "id" diff --git a/gin.go b/gin.go index e516360..f932429 100644 --- a/gin.go +++ b/gin.go @@ -147,6 +147,9 @@ type Engine struct { // UseH2C enable h2c support. UseH2C bool + // ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil. + ContextWithFallback bool + delims render.Delims secureJSONPrefix string HTMLRender render.HTMLRender From ed049dd850fb09f93c6993c829744997269a35b5 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 6 Jun 2022 21:01:40 +0800 Subject: [PATCH 688/912] docs: release v1.8.1 version (#3176) Signed-off-by: Bo-Yi Wu --- CHANGELOG.md | 6 ++++++ version.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e7e83e..1bc51a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Gin ChangeLog +## Gin v1.8.1 + +### ENHANCEMENTS + +* feat(context): add ContextWithFallback feature flag [#3172](https://github.com/gin-gonic/gin/pull/3172) + ## Gin v1.8.0 ## Break Changes diff --git a/version.go b/version.go index 9b0eae2..632ca7d 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.8.0" +const Version = "v1.8.1" From f2182de38c8d19d35d4b4191749216f12512f59c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Jun 2022 07:55:03 +0800 Subject: [PATCH 689/912] chore(deps): bump github.com/stretchr/testify from 1.7.1 to 1.7.2 (#3177) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.1 to 1.7.2. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.7.1...v1.7.2) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 6cdde34..2d2c7dc 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/pelletier/go-toml/v2 v2.0.1 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.7.2 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 google.golang.org/protobuf v1.28.0 @@ -27,5 +27,5 @@ require ( golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect golang.org/x/text v0.3.6 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d06b810..18233de 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= @@ -80,5 +81,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 6c3a1d7063b35e5981af3ce2fb98fe1ee89fdcf6 Mon Sep 17 00:00:00 2001 From: Jordan Day Date: Wed, 8 Jun 2022 21:08:49 -0500 Subject: [PATCH 690/912] Small doc fix on Context's ClientIP() method. (#3180) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index b1ad95e..46bf113 100644 --- a/context.go +++ b/context.go @@ -748,7 +748,7 @@ func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error } // ClientIP implements one best effort algorithm to return the real client IP. -// It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. +// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. // If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). // If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy, // the remote IP (coming from Request.RemoteAddr) is returned. From 05caa5c00e552a27c3bfb659b99c0d79b81fafe4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jun 2022 10:08:56 +0800 Subject: [PATCH 691/912] chore(deps): bump github.com/pelletier/go-toml/v2 from 2.0.1 to 2.0.2 (#3198) Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.1 to 2.0.2. - [Release notes](https://github.com/pelletier/go-toml/releases) - [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml) - [Commits](https://github.com/pelletier/go-toml/compare/v2.0.1...v2.0.2) --- updated-dependencies: - dependency-name: github.com/pelletier/go-toml/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 2d2c7dc..9010346 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/goccy/go-json v0.9.7 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 - github.com/pelletier/go-toml/v2 v2.0.1 + github.com/pelletier/go-toml/v2 v2.0.2 github.com/stretchr/testify v1.7.2 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 diff --git a/go.sum b/go.sum index 18233de..620ecce 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= +github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -48,7 +48,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= From 815122a0f4b95f12295921af7e04e0225eb9a6cc Mon Sep 17 00:00:00 2001 From: LanLanceYuan <92938836+L2ncE@users.noreply.github.com> Date: Wed, 15 Jun 2022 17:31:44 +0800 Subject: [PATCH 692/912] Fix a syntax error in a code comment (#3201) --- tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index 88100ee..956bf4d 100644 --- a/tree.go +++ b/tree.go @@ -455,7 +455,7 @@ walk: // Outer loop for walking the tree if !n.wildChild { // If the path at the end of the loop is not equal to '/' and the current node has no child nodes - // the current node needs to roll back to last vaild skippedNode + // the current node needs to roll back to last valid skippedNode if path != "/" { for l := len(*skippedNodes); l > 0; { skippedNode := (*skippedNodes)[l-1] @@ -572,7 +572,7 @@ walk: // Outer loop for walking the tree if path == prefix { // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node - // the current node needs to roll back to last vaild skippedNode + // the current node needs to roll back to last valid skippedNode if n.handlers == nil && path != "/" { for l := len(*skippedNodes); l > 0; { skippedNode := (*skippedNodes)[l-1] From 12b55b4fe9f8e06da3b8f0c4314b3d68a7051e9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jun 2022 22:06:46 +0800 Subject: [PATCH 693/912] chore(deps): bump github.com/stretchr/testify from 1.7.2 to 1.7.4 (#3207) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.2 to 1.7.4. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.7.2...v1.7.4) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 9010346..f759066 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/pelletier/go-toml/v2 v2.0.2 - github.com/stretchr/testify v1.7.2 + github.com/stretchr/testify v1.7.4 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 google.golang.org/protobuf v1.28.0 diff --git a/go.sum b/go.sum index 620ecce..a590d38 100644 --- a/go.sum +++ b/go.sum @@ -45,11 +45,14 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= +github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= From 6de2245e6265a077326d13cb249378b2d27ad781 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 27 Jun 2022 07:11:41 +0800 Subject: [PATCH 694/912] switch min version of go to 1.15 (#3211) --- .github/workflows/gin.yml | 2 +- README.md | 2 +- debug.go | 2 +- debug_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index e8ef30c..6dc787a 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.14, 1.15, 1.16, 1.17, 1.18] + go: [1.15, 1.16, 1.17, 1.18] test-tags: ['', nomsgpack] include: - os: ubuntu-latest diff --git a/README.md b/README.md index 5cc8321..2477d0b 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. You first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin. +1. You first need [Go](https://golang.org/) installed (**version 1.15+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin diff --git a/debug.go b/debug.go index 25fd7c8..8367de9 100644 --- a/debug.go +++ b/debug.go @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.14+. + debugPrint(`[WARNING] Now Gin requires Go 1.15+. `) } diff --git a/debug_test.go b/debug_test.go index 4ac55fe..5c29a74 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.14+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.15+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } From 92dd245c9bef184fcfb5289f31f4247f9fced87b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jun 2022 10:44:54 +0800 Subject: [PATCH 695/912] chore(deps): bump github.com/stretchr/testify from 1.7.4 to 1.7.5 (#3213) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.4 to 1.7.5. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.7.4...v1.7.5) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f759066..283c6a5 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/pelletier/go-toml/v2 v2.0.2 - github.com/stretchr/testify v1.7.4 + github.com/stretchr/testify v1.7.5 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 google.golang.org/protobuf v1.28.0 diff --git a/go.sum b/go.sum index a590d38..4d6c06e 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= -github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= From 088cdd74d42df20d4e58aa17d3f4403a22e8a545 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Fri, 1 Jul 2022 10:31:31 +0800 Subject: [PATCH 696/912] Fix the value of ginSupportMinGoVer constant by semantic (#3221) --- debug.go | 4 ++-- debug_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/debug.go b/debug.go index 8367de9..b9f8234 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 14 +const ginSupportMinGoVer = 15 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -66,7 +66,7 @@ func getMinVer(v string) (uint64, error) { } func debugPrintWARNINGDefault() { - if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { + if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { debugPrint(`[WARNING] Now Gin requires Go 1.15+. `) diff --git a/debug_test.go b/debug_test.go index 5c29a74..bf0e6ab 100644 --- a/debug_test.go +++ b/debug_test.go @@ -103,7 +103,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { SetMode(TestMode) }) m, e := getMinVer(runtime.Version()) - if e == nil && m <= ginSupportMinGoVer { + if e == nil && m < ginSupportMinGoVer { assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.15+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) From 680be7d928fd120fd974a287d4205d4d5ad7c925 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Fri, 1 Jul 2022 17:38:32 +0800 Subject: [PATCH 697/912] Add some tests for YAML and TOML formats (#3223) --- context_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/context_test.go b/context_test.go index 6c0be54..d09b0ae 100644 --- a/context_test.go +++ b/context_test.go @@ -1060,6 +1060,19 @@ func TestContextRenderYAML(t *testing.T) { assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } +// TestContextRenderTOML tests that the response is serialized as TOML +// and Content-Type is set to application/toml +func TestContextRenderTOML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.TOML(http.StatusCreated, H{"foo": "bar"}) + + assert.Equal(t, http.StatusCreated, w.Code) + assert.Equal(t, "foo = 'bar'\n", w.Body.String()) + assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) +} + // TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf // and Content-Type is set to application/x-protobuf // and we just use the example protobuf to check if the response is correct @@ -1180,6 +1193,36 @@ func TestContextNegotiationWithXML(t *testing.T) { assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } +func TestContextNegotiationWithYAML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + + c.Negotiate(http.StatusOK, Negotiate{ + Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML}, + Data: H{"foo": "bar"}, + }) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "foo: bar\n", w.Body.String()) + assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) +} + +func TestContextNegotiationWithTOML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "", nil) + + c.Negotiate(http.StatusOK, Negotiate{ + Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML}, + Data: H{"foo": "bar"}, + }) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "foo = 'bar'\n", w.Body.String()) + assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) +} + func TestContextNegotiationWithHTML(t *testing.T) { w := httptest.NewRecorder() c, router := CreateTestContext(w) @@ -1640,6 +1683,23 @@ func TestContextBindWithYAML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextBindWithTOML(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo = 'bar'\nbar = 'foo'")) + c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type + + var obj struct { + Foo string `toml:"foo"` + Bar string `toml:"bar"` + } + assert.NoError(t, c.BindTOML(&obj)) + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextBadAutoBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From e837e1cd1850559d91d921b712bc7b0c8f78cf7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 09:56:54 +0800 Subject: [PATCH 698/912] chore(deps): bump github.com/stretchr/testify from 1.7.5 to 1.8.0 (#3229) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.5 to 1.8.0. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.7.5...v1.8.0) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 283c6a5..6371d22 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/pelletier/go-toml/v2 v2.0.2 - github.com/stretchr/testify v1.7.5 + github.com/stretchr/testify v1.8.0 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 google.golang.org/protobuf v1.28.0 diff --git a/go.sum b/go.sum index 4d6c06e..aba0019 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= -github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= From b57163a0e4339d7feb393ff430a454f4e448cf9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 09:58:06 +0800 Subject: [PATCH 699/912] chore(deps): bump github.com/goccy/go-json from 0.9.7 to 0.9.8 (#3228) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.9.7 to 0.9.8. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.9.7...v0.9.8) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6371d22..ae7a5f8 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 - github.com/goccy/go-json v0.9.7 + github.com/goccy/go-json v0.9.8 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/pelletier/go-toml/v2 v2.0.2 diff --git a/go.sum b/go.sum index aba0019..3529342 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE= +github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From c35bde97d5380b48e7736742c3477c08c68047df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 31 Jul 2022 13:02:22 +0800 Subject: [PATCH 700/912] chore(deps): bump github.com/goccy/go-json from 0.9.8 to 0.9.10 (#3251) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ae7a5f8..0b259e1 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 - github.com/goccy/go-json v0.9.8 + github.com/goccy/go-json v0.9.10 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.14 github.com/pelletier/go-toml/v2 v2.0.2 diff --git a/go.sum b/go.sum index 3529342..640ed10 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE= -github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc= +github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From 79dd72deb9edd7120bd0eda36e99f9bfb712d818 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 1 Aug 2022 09:23:45 +0800 Subject: [PATCH 701/912] docs: update markdown format (#3260) Signed-off-by: Bo-Yi Wu --- README.md | 1876 +++++++++++++++++++++++++++-------------------------- 1 file changed, 946 insertions(+), 930 deletions(-) diff --git a/README.md b/README.md index 2477d0b..1c315f8 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. - ## Contents - [Gin Web Framework](#gin-web-framework) @@ -23,7 +22,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Quick start](#quick-start) - [Benchmarks](#benchmarks) - [Gin v1. stable](#gin-v1-stable) - - [Build with jsoniter/go-json](#build-with-json-replacement) + - [Build with json replacement](#build-with-json-replacement) - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature) - [API Examples](#api-examples) - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) @@ -38,6 +37,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Grouping routes](#grouping-routes) - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - [Using middleware](#using-middleware) + - [Custom Recovery behavior](#custom-recovery-behavior) - [How to write log file](#how-to-write-log-file) - [Custom Log Format](#custom-log-format) - [Controlling Log output coloring](#controlling-log-output-coloring) @@ -75,6 +75,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) + - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag) - [http2 server push](#http2-server-push) - [Define format for the log of routes](#define-format-for-the-log-of-routes) - [Set and get a cookie](#set-and-get-a-cookie) @@ -89,7 +90,7 @@ To install Gin package, you need to install Go and set your Go workspace first. 1. You first need [Go](https://golang.org/) installed (**version 1.15+ is required**), then you can use the below Go command to install Gin. ```sh -$ go get -u github.com/gin-gonic/gin +go get -u github.com/gin-gonic/gin ``` 2. Import it in your code: @@ -115,19 +116,19 @@ $ cat example.go package main import ( - "net/http" + "net/http" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) func main() { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "pong", - }) - }) - r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "pong", + }) + }) + r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") } ``` @@ -193,12 +194,15 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr Gin uses `encoding/json` as default json package but you can change it by build from other tags. [jsoniter](https://github.com/json-iterator/go) + ```sh -$ go build -tags=jsoniter . +go build -tags=jsoniter . ``` + [go-json](https://github.com/goccy/go-json) + ```sh -$ go build -tags=go_json . +go build -tags=go_json . ``` ## Build without `MsgPack` rendering feature @@ -206,7 +210,7 @@ $ go build -tags=go_json . Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag. ```sh -$ go build -tags=nomsgpack . +go build -tags=nomsgpack . ``` This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852). @@ -219,22 +223,22 @@ You can find a number of ready-to-run examples at [Gin examples repository](http ```go func main() { - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() - router.GET("/someGet", getting) - router.POST("/somePost", posting) - router.PUT("/somePut", putting) - router.DELETE("/someDelete", deleting) - router.PATCH("/somePatch", patching) - router.HEAD("/someHead", head) - router.OPTIONS("/someOptions", options) + router.GET("/someGet", getting) + router.POST("/somePost", posting) + router.PUT("/somePut", putting) + router.DELETE("/someDelete", deleting) + router.PATCH("/somePatch", patching) + router.HEAD("/someHead", head) + router.OPTIONS("/someOptions", options) - // By default it serves on :8080 unless a - // PORT environment variable was defined. - router.Run() - // router.Run(":3000") for a hard coded port + // By default it serves on :8080 unless a + // PORT environment variable was defined. + router.Run() + // router.Run(":3000") for a hard coded port } ``` @@ -242,37 +246,37 @@ func main() { ```go func main() { - router := gin.Default() + router := gin.Default() - // This handler will match /user/john but will not match /user/ or /user - router.GET("/user/:name", func(c *gin.Context) { - name := c.Param("name") - c.String(http.StatusOK, "Hello %s", name) - }) + // This handler will match /user/john but will not match /user/ or /user + router.GET("/user/:name", func(c *gin.Context) { + name := c.Param("name") + c.String(http.StatusOK, "Hello %s", name) + }) - // However, this one will match /user/john/ and also /user/john/send - // If no other routers match /user/john, it will redirect to /user/john/ - router.GET("/user/:name/*action", func(c *gin.Context) { - name := c.Param("name") - action := c.Param("action") - message := name + " is " + action - c.String(http.StatusOK, message) - }) + // However, this one will match /user/john/ and also /user/john/send + // If no other routers match /user/john, it will redirect to /user/john/ + router.GET("/user/:name/*action", func(c *gin.Context) { + name := c.Param("name") + action := c.Param("action") + message := name + " is " + action + c.String(http.StatusOK, message) + }) - // For each matched request Context will hold the route definition - router.POST("/user/:name/*action", func(c *gin.Context) { - b := c.FullPath() == "/user/:name/*action" // true - c.String(http.StatusOK, "%t", b) - }) + // For each matched request Context will hold the route definition + router.POST("/user/:name/*action", func(c *gin.Context) { + b := c.FullPath() == "/user/:name/*action" // true + c.String(http.StatusOK, "%t", b) + }) - // This handler will add a new router for /user/groups. - // Exact routes are resolved before param routes, regardless of the order they were defined. - // Routes starting with /user/groups are never interpreted as /user/:name/... routes - router.GET("/user/groups", func(c *gin.Context) { - c.String(http.StatusOK, "The available groups are [...]") - }) + // This handler will add a new router for /user/groups. + // Exact routes are resolved before param routes, regardless of the order they were defined. + // Routes starting with /user/groups are never interpreted as /user/:name/... routes + router.GET("/user/groups", func(c *gin.Context) { + c.String(http.StatusOK, "The available groups are [...]") + }) - router.Run(":8080") + router.Run(":8080") } ``` @@ -280,17 +284,17 @@ func main() { ```go func main() { - router := gin.Default() + router := gin.Default() - // Query string parameters are parsed using the existing underlying request object. - // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe - router.GET("/welcome", func(c *gin.Context) { - firstname := c.DefaultQuery("firstname", "Guest") - lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") + // Query string parameters are parsed using the existing underlying request object. + // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe + router.GET("/welcome", func(c *gin.Context) { + firstname := c.DefaultQuery("firstname", "Guest") + lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") - c.String(http.StatusOK, "Hello %s %s", firstname, lastname) - }) - router.Run(":8080") + c.String(http.StatusOK, "Hello %s %s", firstname, lastname) + }) + router.Run(":8080") } ``` @@ -298,25 +302,25 @@ func main() { ```go func main() { - router := gin.Default() + router := gin.Default() - router.POST("/form_post", func(c *gin.Context) { - message := c.PostForm("message") - nick := c.DefaultPostForm("nick", "anonymous") + router.POST("/form_post", func(c *gin.Context) { + message := c.PostForm("message") + nick := c.DefaultPostForm("nick", "anonymous") - c.JSON(http.StatusOK, gin.H{ - "status": "posted", - "message": message, - "nick": nick, - }) - }) - router.Run(":8080") + c.JSON(http.StatusOK, gin.H{ + "status": "posted", + "message": message, + "nick": nick, + }) + }) + router.Run(":8080") } ``` ### Another example: query + post form -``` +```sh POST /post?id=1234&page=1 HTTP/1.1 Content-Type: application/x-www-form-urlencoded @@ -325,28 +329,28 @@ name=manu&message=this_is_great ```go func main() { - router := gin.Default() + router := gin.Default() - router.POST("/post", func(c *gin.Context) { + router.POST("/post", func(c *gin.Context) { - id := c.Query("id") - page := c.DefaultQuery("page", "0") - name := c.PostForm("name") - message := c.PostForm("message") + id := c.Query("id") + page := c.DefaultQuery("page", "0") + name := c.PostForm("name") + message := c.PostForm("message") - fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) - }) - router.Run(":8080") + fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) + }) + router.Run(":8080") } ``` -``` +```sh id: 1234; page: 1; name: manu; message: this_is_great ``` ### Map as querystring or postform parameters -``` +```sh POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 Content-Type: application/x-www-form-urlencoded @@ -355,20 +359,20 @@ names[first]=thinkerou&names[second]=tianou ```go func main() { - router := gin.Default() + router := gin.Default() - router.POST("/post", func(c *gin.Context) { + router.POST("/post", func(c *gin.Context) { - ids := c.QueryMap("ids") - names := c.PostFormMap("names") + ids := c.QueryMap("ids") + names := c.PostFormMap("names") - fmt.Printf("ids: %v; names: %v", ids, names) - }) - router.Run(":8080") + fmt.Printf("ids: %v; names: %v", ids, names) + }) + router.Run(":8080") } ``` -``` +```sh ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou] ``` @@ -384,20 +388,20 @@ 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) + 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) + // Upload the file to specific dst. + c.SaveUploadedFile(file, dst) - c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) - }) - router.Run(":8080") + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) + }) + router.Run(":8080") } ``` @@ -415,23 +419,23 @@ See the detail [example code](https://github.com/gin-gonic/examples/tree/master/ ```go func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // Multipart form - form, _ := c.MultipartForm() - files := form.File["upload[]"] - - for _, file := range files { - log.Println(file.Filename) - - // Upload the file to specific dst. - c.SaveUploadedFile(file, dst) - } - c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) - }) - router.Run(":8080") + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // Multipart form + form, _ := c.MultipartForm() + files := form.File["upload[]"] + + for _, file := range files { + log.Println(file.Filename) + + // Upload the file to specific dst. + c.SaveUploadedFile(file, dst) + } + c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) + }) + router.Run(":8080") } ``` @@ -448,25 +452,25 @@ curl -X POST http://localhost:8080/upload \ ```go func main() { - router := gin.Default() - - // Simple group: v1 - v1 := router.Group("/v1") - { - v1.POST("/login", loginEndpoint) - v1.POST("/submit", submitEndpoint) - v1.POST("/read", readEndpoint) - } + router := gin.Default() + + // Simple group: v1 + v1 := router.Group("/v1") + { + v1.POST("/login", loginEndpoint) + v1.POST("/submit", submitEndpoint) + v1.POST("/read", readEndpoint) + } - // Simple group: v2 - v2 := router.Group("/v2") - { - v2.POST("/login", loginEndpoint) - v2.POST("/submit", submitEndpoint) - v2.POST("/read", readEndpoint) - } + // Simple group: v2 + v2 := router.Group("/v2") + { + v2.POST("/login", loginEndpoint) + v2.POST("/submit", submitEndpoint) + v2.POST("/read", readEndpoint) + } - router.Run(":8080") + router.Run(":8080") } ``` @@ -485,81 +489,83 @@ instead of r := gin.Default() ``` - ### Using middleware + ```go func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. - // By default gin.DefaultWriter = os.Stdout - r.Use(gin.Logger()) - - // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.Recovery()) - - // Per route middleware, you can add as many as you desire. - r.GET("/benchmark", MyBenchLogger(), benchEndpoint) - - // Authorization group - // authorized := r.Group("/", AuthRequired()) - // exactly the same as: - authorized := r.Group("/") - // per group middleware! in this case we use the custom created - // AuthRequired() middleware just in the "authorized" group. - authorized.Use(AuthRequired()) - { - authorized.POST("/login", loginEndpoint) - authorized.POST("/submit", submitEndpoint) - authorized.POST("/read", readEndpoint) - - // nested group - testing := authorized.Group("testing") - // visit 0.0.0.0:8080/testing/analytics - testing.GET("/analytics", analyticsEndpoint) - } + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.Recovery()) + + // Per route middleware, you can add as many as you desire. + r.GET("/benchmark", MyBenchLogger(), benchEndpoint) + + // Authorization group + // authorized := r.Group("/", AuthRequired()) + // exactly the same as: + authorized := r.Group("/") + // per group middleware! in this case we use the custom created + // AuthRequired() middleware just in the "authorized" group. + authorized.Use(AuthRequired()) + { + authorized.POST("/login", loginEndpoint) + authorized.POST("/submit", submitEndpoint) + authorized.POST("/read", readEndpoint) + + // nested group + testing := authorized.Group("testing") + // visit 0.0.0.0:8080/testing/analytics + testing.GET("/analytics", analyticsEndpoint) + } - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") } ``` ### Custom Recovery behavior + ```go func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. - // By default gin.DefaultWriter = os.Stdout - r.Use(gin.Logger()) - - // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { - if err, ok := recovered.(string); ok { - c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) - } - c.AbortWithStatus(http.StatusInternalServerError) - })) + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { + if err, ok := recovered.(string); ok { + c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) + } + c.AbortWithStatus(http.StatusInternalServerError) + })) - r.GET("/panic", func(c *gin.Context) { - // panic with a string -- the custom middleware could save this to a database or report it to the user - panic("foo") - }) + r.GET("/panic", func(c *gin.Context) { + // panic with a string -- the custom middleware could save this to a database or report it to the user + panic("foo") + }) - r.GET("/", func(c *gin.Context) { - c.String(http.StatusOK, "ohai") - }) + r.GET("/", func(c *gin.Context) { + c.String(http.StatusOK, "ohai") + }) - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") } ``` ### How to write log file + ```go func main() { // Disable Console Color, you don't need console color when writing the logs to file. @@ -582,39 +588,41 @@ func main() { ``` ### Custom Log Format + ```go func main() { - router := gin.New() + router := gin.New() - // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter - // By default gin.DefaultWriter = os.Stdout - router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { - // your custom format - return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", - param.ClientIP, - param.TimeStamp.Format(time.RFC1123), - param.Method, - param.Path, - param.Request.Proto, - param.StatusCode, - param.Latency, - param.Request.UserAgent(), - param.ErrorMessage, - ) - })) - router.Use(gin.Recovery()) + // your custom format + return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", + param.ClientIP, + param.TimeStamp.Format(time.RFC1123), + param.Method, + param.Path, + param.Request.Proto, + param.StatusCode, + param.Latency, + param.Request.UserAgent(), + param.ErrorMessage, + ) + })) + router.Use(gin.Recovery()) - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) - router.Run(":8080") + router.Run(":8080") } ``` -**Sample Output** -``` +Sample Output + +```sh ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " ``` @@ -669,6 +677,7 @@ Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/vali Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. Also, Gin provides two sets of methods for binding: + - **Type** - Must bind - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML` - **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. @@ -683,74 +692,75 @@ You can also specify that specific fields are required. If a field is decorated ```go // Binding from JSON type Login struct { - User string `form:"user" json:"user" xml:"user" binding:"required"` - Password string `form:"password" json:"password" xml:"password" binding:"required"` + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` } func main() { - router := gin.Default() - - // Example for binding JSON ({"user": "manu", "password": "123"}) - router.POST("/loginJSON", func(c *gin.Context) { - var json Login - if err := c.ShouldBindJSON(&json); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if json.User != "manu" || json.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding XML ( - // - // - // manu - // 123 - // ) - router.POST("/loginXML", func(c *gin.Context) { - var xml Login - if err := c.ShouldBindXML(&xml); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if xml.User != "manu" || xml.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding a HTML form (user=manu&password=123) - router.POST("/loginForm", func(c *gin.Context) { - var form Login - // This will infer what binder to use depending on the content-type header. - if err := c.ShouldBind(&form); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if form.User != "manu" || form.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") -} -``` - -**Sample request** -```shell + router := gin.Default() + + // Example for binding JSON ({"user": "manu", "password": "123"}) + router.POST("/loginJSON", func(c *gin.Context) { + var json Login + if err := c.ShouldBindJSON(&json); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding XML ( + // + // + // manu + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding a HTML form (user=manu&password=123) + router.POST("/loginForm", func(c *gin.Context) { + var form Login + // This will infer what binder to use depending on the content-type header. + if err := c.ShouldBind(&form); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Listen and serve on 0.0.0.0:8080 + router.Run(":8080") +} +``` + +Sample request + +```sh $ curl -v -X POST \ http://localhost:8080/loginJSON \ -H 'content-type: application/json' \ @@ -771,9 +781,7 @@ $ curl -v -X POST \ {"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. +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 @@ -783,49 +791,49 @@ It is also possible to register custom validators. See the [example code](https: package main import ( - "net/http" - "time" + "net/http" + "time" - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - "github.com/go-playground/validator/v10" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" ) // Booking contains binded and validated data. 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"` + 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"` } var bookableDate validator.Func = func(fl validator.FieldLevel) bool { - date, ok := fl.Field().Interface().(time.Time) - if ok { - today := time.Now() - if today.After(date) { - return false - } - } - return true + date, ok := fl.Field().Interface().(time.Time) + if ok { + today := time.Now() + if today.After(date) { + return false + } + } + return true } func main() { - route := gin.Default() + route := gin.Default() - if v, ok := binding.Validator.Engine().(*validator.Validate); ok { - v.RegisterValidation("bookabledate", bookableDate) - } + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } - route.GET("/bookable", getBookable) - route.Run(":8085") + 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()}) - } + 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()}) + } } ``` @@ -851,31 +859,31 @@ See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tr package main import ( - "log" - "net/http" + "log" + "net/http" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) type Person struct { - Name string `form:"name"` - Address string `form:"address"` + Name string `form:"name"` + Address string `form:"address"` } func main() { - route := gin.Default() - route.Any("/testing", startPage) - route.Run(":8085") + 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(http.StatusOK, "Success") + 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(http.StatusOK, "Success") } ``` @@ -888,11 +896,11 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/742#issueco package main import ( - "log" - "net/http" - "time" + "log" + "net/http" + "time" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) type Person struct { @@ -904,16 +912,16 @@ type Person struct { } func main() { - route := gin.Default() - route.GET("/testing", startPage) - route.Run(":8085") + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") } func startPage(c *gin.Context) { - var person Person - // If `GET`, only `Form` binding engine (`query`) used. - // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). - // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88 + var person Person + // If `GET`, only `Form` binding engine (`query`) used. + // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88 if c.ShouldBind(&person) == nil { log.Println(person.Name) log.Println(person.Address) @@ -922,13 +930,14 @@ func startPage(c *gin.Context) { log.Println(person.UnixTime) } - c.String(http.StatusOK, "Success") + c.String(http.StatusOK, "Success") } ``` Test it with: + ```sh -$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" +curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" ``` ### Bind Uri @@ -939,34 +948,35 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/846). package main import ( - "net/http" + "net/http" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) type Person struct { - ID string `uri:"id" binding:"required,uuid"` - Name string `uri:"name" binding:"required"` + ID string `uri:"id" binding:"required,uuid"` + Name string `uri:"name" binding:"required"` } func main() { - route := gin.Default() - route.GET("/:name/:id", func(c *gin.Context) { - var person Person - if err := c.ShouldBindUri(&person); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) - }) - route.Run(":8088") + route := gin.Default() + route.GET("/:name/:id", func(c *gin.Context) { + var person Person + if err := c.ShouldBindUri(&person); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) + }) + route.Run(":8088") } ``` Test it with: + ```sh -$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 -$ curl -v localhost:8088/thinkerou/not-uuid +curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 +curl -v localhost:8088/thinkerou/not-uuid ``` ### Bind Header @@ -975,31 +985,31 @@ $ curl -v localhost:8088/thinkerou/not-uuid package main import ( - "fmt" - "net/http" + "fmt" + "net/http" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) type testHeader struct { - Rate int `header:"Rate"` - Domain string `header:"Domain"` + Rate int `header:"Rate"` + Domain string `header:"Domain"` } func main() { - r := gin.Default() - r.GET("/", func(c *gin.Context) { - h := testHeader{} + r := gin.Default() + r.GET("/", func(c *gin.Context) { + h := testHeader{} - if err := c.ShouldBindHeader(&h); err != nil { - c.JSON(http.StatusOK, err) - } + if err := c.ShouldBindHeader(&h); err != nil { + c.JSON(http.StatusOK, err) + } - fmt.Printf("%#v\n", h) - c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain}) - }) + fmt.Printf("%#v\n", h) + c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain}) + }) - r.Run() + r.Run() // client // curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ @@ -1050,7 +1060,7 @@ form.html result: -``` +```json {"color":["red","green","blue"]} ``` @@ -1058,94 +1068,95 @@ result: ```go type ProfileForm struct { - Name string `form:"name" binding:"required"` - Avatar *multipart.FileHeader `form:"avatar" binding:"required"` + Name string `form:"name" binding:"required"` + Avatar *multipart.FileHeader `form:"avatar" binding:"required"` - // or for multiple files - // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"` + // or for multiple files + // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"` } func main() { - router := gin.Default() - router.POST("/profile", func(c *gin.Context) { - // you can bind multipart form with explicit binding declaration: - // c.ShouldBindWith(&form, binding.Form) - // or you can simply use autobinding with ShouldBind method: - var form ProfileForm - // in this case proper binding will be automatically selected - if err := c.ShouldBind(&form); err != nil { - c.String(http.StatusBadRequest, "bad request") - return - } + router := gin.Default() + router.POST("/profile", func(c *gin.Context) { + // you can bind multipart form with explicit binding declaration: + // c.ShouldBindWith(&form, binding.Form) + // or you can simply use autobinding with ShouldBind method: + var form ProfileForm + // in this case proper binding will be automatically selected + if err := c.ShouldBind(&form); err != nil { + c.String(http.StatusBadRequest, "bad request") + return + } - err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename) - if err != nil { - c.String(http.StatusInternalServerError, "unknown error") - return - } + err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename) + if err != nil { + c.String(http.StatusInternalServerError, "unknown error") + return + } - // db.Save(&form) + // db.Save(&form) - c.String(http.StatusOK, "ok") - }) - router.Run(":8080") + c.String(http.StatusOK, "ok") + }) + router.Run(":8080") } ``` Test it with: + ```sh -$ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile +curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile ``` ### XML, JSON, YAML and ProtoBuf rendering ```go func main() { - r := gin.Default() - - // gin.H is a shortcut for map[string]interface{} - r.GET("/someJSON", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/moreJSON", func(c *gin.Context) { - // You also can use a struct - var msg struct { - Name string `json:"user"` - Message string - Number int - } - msg.Name = "Lena" - msg.Message = "hey" - msg.Number = 123 - // Note that msg.Name becomes "user" in the JSON - // Will output : {"user": "Lena", "Message": "hey", "Number": 123} - c.JSON(http.StatusOK, msg) - }) - - r.GET("/someXML", func(c *gin.Context) { - c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/someYAML", func(c *gin.Context) { - c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) - }) - - r.GET("/someProtoBuf", func(c *gin.Context) { - reps := []int64{int64(1), int64(2)} - label := "test" - // The specific definition of protobuf is written in the testdata/protoexample file. - data := &protoexample.Test{ - Label: &label, - Reps: reps, - } - // Note that data becomes binary data in the response - // Will output protoexample.Test protobuf serialized data - c.ProtoBuf(http.StatusOK, data) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") + r := gin.Default() + + // gin.H is a shortcut for map[string]interface{} + r.GET("/someJSON", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/moreJSON", func(c *gin.Context) { + // You also can use a struct + var msg struct { + Name string `json:"user"` + Message string + Number int + } + msg.Name = "Lena" + msg.Message = "hey" + msg.Number = 123 + // Note that msg.Name becomes "user" in the JSON + // Will output : {"user": "Lena", "Message": "hey", "Number": 123} + c.JSON(http.StatusOK, msg) + }) + + r.GET("/someXML", func(c *gin.Context) { + c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/someYAML", func(c *gin.Context) { + c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + + r.GET("/someProtoBuf", func(c *gin.Context) { + reps := []int64{int64(1), int64(2)} + label := "test" + // The specific definition of protobuf is written in the testdata/protoexample file. + data := &protoexample.Test{ + Label: &label, + Reps: reps, + } + // Note that data becomes binary data in the response + // Will output protoexample.Test protobuf serialized data + c.ProtoBuf(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -1155,42 +1166,43 @@ Using SecureJSON to prevent json hijacking. Default prepends `"while(1),"` to re ```go func main() { - r := gin.Default() + r := gin.Default() - // You can also use your own secure json prefix - // r.SecureJsonPrefix(")]}',\n") + // You can also use your own secure json prefix + // r.SecureJsonPrefix(")]}',\n") - r.GET("/someJSON", func(c *gin.Context) { - names := []string{"lena", "austin", "foo"} + r.GET("/someJSON", func(c *gin.Context) { + names := []string{"lena", "austin", "foo"} - // Will output : while(1);["lena","austin","foo"] - c.SecureJSON(http.StatusOK, names) - }) + // Will output : while(1);["lena","austin","foo"] + c.SecureJSON(http.StatusOK, names) + }) - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") + // 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 := gin.Default() - r.GET("/JSONP", func(c *gin.Context) { - data := gin.H{ - "foo": "bar", - } + r.GET("/JSONP", func(c *gin.Context) { + data := gin.H{ + "foo": "bar", + } - //callback is x - // Will output : x({\"foo\":\"bar\"}) - c.JSONP(http.StatusOK, data) - }) + //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") + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") // client // curl http://127.0.0.1:8080/JSONP?callback=x @@ -1203,20 +1215,20 @@ Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters. ```go func main() { - r := gin.Default() + r := gin.Default() - r.GET("/someJSON", func(c *gin.Context) { - data := gin.H{ - "lang": "GO语言", - "tag": "
", - } + r.GET("/someJSON", func(c *gin.Context) { + data := gin.H{ + "lang": "GO语言", + "tag": "
", + } - // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} - c.AsciiJSON(http.StatusOK, data) - }) + // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} + c.AsciiJSON(http.StatusOK, data) + }) - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -1227,24 +1239,24 @@ This feature is unavailable in Go 1.6 and lower. ```go func main() { - r := gin.Default() + r := gin.Default() - // Serves unicode entities - r.GET("/json", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "html": "Hello, world!", - }) - }) + // Serves unicode entities + r.GET("/json", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "html": "Hello, world!", + }) + }) - // Serves literal characters - r.GET("/purejson", func(c *gin.Context) { - c.PureJSON(http.StatusOK, gin.H{ - "html": "Hello, world!", - }) - }) + // Serves literal characters + r.GET("/purejson", func(c *gin.Context) { + c.PureJSON(http.StatusOK, gin.H{ + "html": "Hello, world!", + }) + }) - // listen and serve on 0.0.0.0:8080 - r.Run(":8080") + // listen and serve on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -1252,14 +1264,14 @@ func main() { ```go func main() { - router := gin.Default() - router.Static("/assets", "./assets") - router.StaticFS("/more_static", http.Dir("my_file_system")) - router.StaticFile("/favicon.ico", "./resources/favicon.ico") - router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system")) - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") + router := gin.Default() + router.Static("/assets", "./assets") + router.StaticFS("/more_static", http.Dir("my_file_system")) + router.StaticFile("/favicon.ico", "./resources/favicon.ico") + router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system")) + + // Listen and serve on 0.0.0.0:8080 + router.Run(":8080") } ``` @@ -1267,16 +1279,16 @@ func main() { ```go func main() { - router := gin.Default() + router := gin.Default() - router.GET("/local/file", func(c *gin.Context) { - c.File("local/file.go") - }) + router.GET("/local/file", func(c *gin.Context) { + c.File("local/file.go") + }) - var fs http.FileSystem = // ... - router.GET("/fs/file", func(c *gin.Context) { - c.FileFromFS("fs/file.go", fs) - }) + var fs http.FileSystem = // ... + router.GET("/fs/file", func(c *gin.Context) { + c.FileFromFS("fs/file.go", fs) + }) } ``` @@ -1285,26 +1297,26 @@ func main() { ```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 - } + 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 - defer reader.Close() - contentLength := response.ContentLength - contentType := response.Header.Get("Content-Type") + reader := response.Body + defer reader.Close() + contentLength := response.ContentLength + contentType := response.Header.Get("Content-Type") - extraHeaders := map[string]string{ - "Content-Disposition": `attachment; filename="gopher.png"`, - } + extraHeaders := map[string]string{ + "Content-Disposition": `attachment; filename="gopher.png"`, + } - c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) - }) - router.Run(":8080") + c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) + }) + router.Run(":8080") } ``` @@ -1314,15 +1326,15 @@ Using LoadHTMLGlob() or LoadHTMLFiles() ```go func main() { - router := gin.Default() - router.LoadHTMLGlob("templates/*") - //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") - router.GET("/index", func(c *gin.Context) { - c.HTML(http.StatusOK, "index.tmpl", gin.H{ - "title": "Main website", - }) - }) - router.Run(":8080") + router := gin.Default() + router.LoadHTMLGlob("templates/*") + //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") + router.GET("/index", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.tmpl", gin.H{ + "title": "Main website", + }) + }) + router.Run(":8080") } ``` @@ -1330,9 +1342,9 @@ templates/index.tmpl ```html -

- {{ .title }} -

+

+ {{ .title }} +

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

- {{ .title }} + {{ .title }}

Using posts/index.tmpl

@@ -1373,7 +1385,7 @@ templates/users/index.tmpl ```html {{ define "users/index.tmpl" }}

- {{ .title }} + {{ .title }}

Using users/index.tmpl

@@ -1388,10 +1400,10 @@ You can also use your own html template render import "html/template" func main() { - router := gin.Default() - html := template.Must(template.ParseFiles("file1", "file2")) - router.SetHTMLTemplate(html) - router.Run(":8080") + router := gin.Default() + html := template.Must(template.ParseFiles("file1", "file2")) + router.SetHTMLTemplate(html) + router.Run(":8080") } ``` @@ -1400,9 +1412,9 @@ func main() { You may use custom delims ```go - r := gin.Default() - r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates") + r := gin.Default() + r.Delims("{[{", "}]}") + r.LoadHTMLGlob("/path/to/templates") ``` #### Custom Template Funcs @@ -1452,7 +1464,8 @@ Date: {[{.now | formatAsDate}]} ``` Result: -``` + +```sh Date: 2017/07/01 ``` @@ -1466,14 +1479,15 @@ Issuing a HTTP redirect is easy. Both internal and external locations are suppor ```go r.GET("/test", func(c *gin.Context) { - c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") + c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") }) ``` Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444) + ```go r.POST("/test", func(c *gin.Context) { - c.Redirect(http.StatusFound, "/foo") + c.Redirect(http.StatusFound, "/foo") }) ``` @@ -1489,44 +1503,43 @@ r.GET("/test2", func(c *gin.Context) { }) ``` - ### Custom Middleware ```go func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - t := time.Now() + return func(c *gin.Context) { + t := time.Now() - // Set example variable - c.Set("example", "12345") + // Set example variable + c.Set("example", "12345") - // before request + // before request - c.Next() + c.Next() - // after request - latency := time.Since(t) - log.Print(latency) + // after request + latency := time.Since(t) + log.Print(latency) - // access the status we are sending - status := c.Writer.Status() - log.Println(status) - } + // access the status we are sending + status := c.Writer.Status() + log.Println(status) + } } func main() { - r := gin.New() - r.Use(Logger()) + r := gin.New() + r.Use(Logger()) - r.GET("/test", func(c *gin.Context) { - example := c.MustGet("example").(string) + r.GET("/test", func(c *gin.Context) { + example := c.MustGet("example").(string) - // it would print: "12345" - log.Println(example) - }) + // it would print: "12345" + log.Println(example) + }) - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -1535,37 +1548,37 @@ func main() { ```go // simulate some private data var secrets = gin.H{ - "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, - "austin": gin.H{"email": "austin@example.com", "phone": "666"}, - "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, + "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, + "austin": gin.H{"email": "austin@example.com", "phone": "666"}, + "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, } func main() { - r := gin.Default() - - // Group using gin.BasicAuth() middleware - // gin.Accounts is a shortcut for map[string]string - authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ - "foo": "bar", - "austin": "1234", - "lena": "hello2", - "manu": "4321", - })) - - // /admin/secrets endpoint - // hit "localhost:8080/admin/secrets - authorized.GET("/secrets", func(c *gin.Context) { - // get user, it was set by the BasicAuth middleware - user := c.MustGet(gin.AuthUserKey).(string) - if secret, ok := secrets[user]; ok { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) - } else { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) - } - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") + r := gin.Default() + + // Group using gin.BasicAuth() middleware + // gin.Accounts is a shortcut for map[string]string + authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ + "foo": "bar", + "austin": "1234", + "lena": "hello2", + "manu": "4321", + })) + + // /admin/secrets endpoint + // hit "localhost:8080/admin/secrets + authorized.GET("/secrets", func(c *gin.Context) { + // get user, it was set by the BasicAuth middleware + user := c.MustGet(gin.AuthUserKey).(string) + if secret, ok := secrets[user]; ok { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) + } else { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) + } + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -1575,30 +1588,30 @@ When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** ```go func main() { - r := gin.Default() + r := gin.Default() - r.GET("/long_async", func(c *gin.Context) { - // create copy to be used inside the goroutine - cCp := c.Copy() - go func() { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) + r.GET("/long_async", func(c *gin.Context) { + // create copy to be used inside the goroutine + cCp := c.Copy() + go func() { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) - // note that you are using the copied context "cCp", IMPORTANT - log.Println("Done! in path " + cCp.Request.URL.Path) - }() - }) + // note that you are using the copied context "cCp", IMPORTANT + log.Println("Done! in path " + cCp.Request.URL.Path) + }() + }) - r.GET("/long_sync", func(c *gin.Context) { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) + r.GET("/long_sync", func(c *gin.Context) { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) - // since we are NOT using a goroutine, we do not have to copy the context - log.Println("Done! in path " + c.Request.URL.Path) - }) + // since we are NOT using a goroutine, we do not have to copy the context + log.Println("Done! in path " + c.Request.URL.Path) + }) - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") } ``` @@ -1608,24 +1621,25 @@ Use `http.ListenAndServe()` directly, like this: ```go func main() { - router := gin.Default() - http.ListenAndServe(":8080", router) + router := gin.Default() + http.ListenAndServe(":8080", router) } ``` + or ```go func main() { - router := gin.Default() - - s := &http.Server{ - Addr: ":8080", - Handler: router, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - s.ListenAndServe() + router := gin.Default() + + s := &http.Server{ + Addr: ":8080", + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + s.ListenAndServe() } ``` @@ -1637,22 +1651,22 @@ example for 1-line LetsEncrypt HTTPS servers. package main import ( - "log" - "net/http" + "log" + "net/http" - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" ) func main() { - r := gin.Default() + r := gin.Default() - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) - log.Fatal(autotls.Run(r, "example1.com", "example2.com")) + log.Fatal(autotls.Run(r, "example1.com", "example2.com")) } ``` @@ -1662,29 +1676,29 @@ example for custom autocert manager. package main import ( - "log" - "net/http" + "log" + "net/http" - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" - "golang.org/x/crypto/acme/autocert" + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/acme/autocert" ) func main() { - r := gin.Default() + r := gin.Default() - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), - Cache: autocert.DirCache("/var/www/.cache"), - } + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), + Cache: autocert.DirCache("/var/www/.cache"), + } - log.Fatal(autotls.RunWithManager(r, &m)) + log.Fatal(autotls.RunWithManager(r, &m)) } ``` @@ -1696,84 +1710,84 @@ See the [question](https://github.com/gin-gonic/gin/issues/346) and try the foll package main import ( - "log" - "net/http" - "time" + "log" + "net/http" + "time" - "github.com/gin-gonic/gin" - "golang.org/x/sync/errgroup" + "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" ) var ( - g errgroup.Group + 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", - }, - ) - }) + 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 + 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", - }, - ) - }) + 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 + return e } func main() { - server01 := &http.Server{ - Addr: ":8080", - Handler: router01(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - server02 := &http.Server{ - Addr: ":8081", - Handler: router02(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - g.Go(func() error { - err := server01.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Fatal(err) - } - return err - }) - - g.Go(func() error { - err := server02.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Fatal(err) - } - return err - }) - - if err := g.Wait(); err != nil { - log.Fatal(err) - } + server01 := &http.Server{ + Addr: ":8080", + Handler: router01(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + server02 := &http.Server{ + Addr: ":8081", + Handler: router02(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + g.Go(func() error { + err := server01.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + return err + }) + + g.Go(func() error { + err := server02.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + return err + }) + + if err := g.Wait(); err != nil { + log.Fatal(err) + } } ``` @@ -1794,9 +1808,9 @@ endless.ListenAndServe(":4242", router) Alternatives: -* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. -* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. * [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. +* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. +* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. #### Manually @@ -1808,57 +1822,57 @@ In case you are using Go 1.8 or a later version, you may not need to use those l package main import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/gin-gonic/gin" + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gin-gonic/gin" ) func main() { - router := gin.Default() - router.GET("/", func(c *gin.Context) { - time.Sleep(5 * time.Second) - c.String(http.StatusOK, "Welcome Gin Server") - }) - - srv := &http.Server{ - Addr: ":8080", - Handler: router, - } - - // Initializing the server in a goroutine so that - // it won't block the graceful shutdown handling below - go func() { - if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) { - log.Printf("listen: %s\n", err) - } - }() - - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 5 seconds. - quit := make(chan os.Signal) - // kill (no param) default send syscall.SIGTERM - // kill -2 is syscall.SIGINT - // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Println("Shutting down server...") - - // The context is used to inform the server it has 5 seconds to finish - // the request it is currently handling - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server forced to shutdown:", err) - } - - log.Println("Server exiting") + router := gin.Default() + router.GET("/", func(c *gin.Context) { + time.Sleep(5 * time.Second) + c.String(http.StatusOK, "Welcome Gin Server") + }) + + srv := &http.Server{ + Addr: ":8080", + Handler: router, + } + + // Initializing the server in a goroutine so that + // it won't block the graceful shutdown handling below + go func() { + if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) { + log.Printf("listen: %s\n", err) + } + }() + + // Wait for interrupt signal to gracefully shutdown the server with + // a timeout of 5 seconds. + quit := make(chan os.Signal) + // kill (no param) default send syscall.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + log.Println("Shutting down server...") + + // The context is used to inform the server it has 5 seconds to finish + // the request it is currently handling + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := srv.Shutdown(ctx); err != nil { + log.Fatal("Server forced to shutdown:", err) + } + + log.Println("Server exiting") } ``` @@ -1870,38 +1884,38 @@ You can build a server into a single binary containing templates by using [go-as ```go func main() { - r := gin.New() + r := gin.New() - t, err := loadTemplate() - if err != nil { - panic(err) - } - r.SetHTMLTemplate(t) + 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") + r.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/index.tmpl",nil) + }) + r.Run(":8080") } // loadTemplate loads templates embedded by go-assets-builder func loadTemplate() (*template.Template, error) { - t := template.New("") - for name, file := range Assets.Files { - defer file.Close() - if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { - continue - } - h, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - t, err = t.New(name).Parse(string(h)) - if err != nil { - return nil, err - } - } - return t, nil + t := template.New("") + for name, file := range Assets.Files { + defer file.Close() + if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { + continue + } + h, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + t, err = t.New(name).Parse(string(h)) + if err != nil { + return nil, err + } + } + return t, nil } ``` @@ -1972,7 +1986,7 @@ func main() { Using the command `curl` command result: -``` +```sh $ curl "http://localhost:8080/getb?field_a=hello&field_b=world" {"a":{"FieldA":"hello"},"b":"world"} $ curl "http://localhost:8080/getc?field_a=hello&field_c=world" @@ -2031,10 +2045,10 @@ func SomeHandler(c *gin.Context) { } ``` -* `c.ShouldBindBodyWith` stores body into the context before binding. This has +1. `c.ShouldBindBodyWith` stores body into the context before binding. This has a slight impact to performance, so you should not use this method if you are enough to call binding at once. -* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, +2. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, `ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, can be called by `c.ShouldBind()` multiple times without any damage to performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). @@ -2043,54 +2057,54 @@ performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). ```go const ( - customerTag = "url" - defaultMemory = 32 << 20 + customerTag = "url" + defaultMemory = 32 << 20 ) type customerBinding struct {} func (customerBinding) Name() string { - return "form" + return "form" } func (customerBinding) Bind(req *http.Request, obj interface{}) error { - if err := req.ParseForm(); err != nil { - return err - } - if err := req.ParseMultipartForm(defaultMemory); err != nil { - if err != http.ErrNotMultipart { - return err - } - } - if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { - return err - } - return validate(obj) + if err := req.ParseForm(); err != nil { + return err + } + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } + if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { + return err + } + return validate(obj) } func validate(obj interface{}) error { - if binding.Validator == nil { - return nil - } - return binding.Validator.ValidateStruct(obj) + if binding.Validator == nil { + return nil + } + return binding.Validator.ValidateStruct(obj) } // Now we can do this!!! // FormA is a external type that we can't modify it's tag type FormA struct { - FieldA string `url:"field_a"` + FieldA string `url:"field_a"` } func ListHandler(s *Service) func(ctx *gin.Context) { - return func(ctx *gin.Context) { - var urlBinding = customerBinding{} - var opt FormA - err := ctx.MustBindWith(&opt, urlBinding) - if err != nil { - ... - } - ... - } + return func(ctx *gin.Context) { + var urlBinding = customerBinding{} + var opt FormA + err := ctx.MustBindWith(&opt, urlBinding) + if err != nil { + ... + } + ... + } } ``` @@ -2102,11 +2116,11 @@ http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.gol package main import ( - "html/template" - "log" - "net/http" + "html/template" + "log" + "net/http" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) var html = template.Must(template.New("https").Parse(` @@ -2122,31 +2136,32 @@ var html = template.Must(template.New("https").Parse(` `)) func main() { - r := gin.Default() - r.Static("/assets", "./assets") - r.SetHTMLTemplate(html) - - r.GET("/", func(c *gin.Context) { - if pusher := c.Writer.Pusher(); pusher != nil { - // use pusher.Push() to do server push - if err := pusher.Push("/assets/app.js", nil); err != nil { - log.Printf("Failed to push: %v", err) - } - } - c.HTML(http.StatusOK, "https", gin.H{ - "status": "success", - }) - }) + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(http.StatusOK, "https", gin.H{ + "status": "success", + }) + }) - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") } ``` ### Define format for the log of routes The default log of routes is: -``` + +```sh [GIN-debug] POST /foo --> main.main.func1 (3 handlers) [GIN-debug] GET /bar --> main.main.func2 (3 handlers) [GIN-debug] GET /status --> main.main.func3 (3 handlers) @@ -2154,34 +2169,35 @@ The default log of routes is: If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. + ```go import ( - "log" - "net/http" + "log" + "net/http" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) func main() { - r := gin.Default() - gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { - log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) - } + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } - r.POST("/foo", func(c *gin.Context) { - c.JSON(http.StatusOK, "foo") - }) + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) - r.GET("/bar", func(c *gin.Context) { - c.JSON(http.StatusOK, "bar") - }) + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) - r.GET("/status", func(c *gin.Context) { - c.JSON(http.StatusOK, "ok") - }) + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) - // Listen and Server in http://0.0.0.0:8080 - r.Run() + // Listen and Server in http://0.0.0.0:8080 + r.Run() } ``` @@ -2233,52 +2249,53 @@ unnecessary computation. ```go import ( - "fmt" + "fmt" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) func main() { - router := gin.Default() - router.SetTrustedProxies([]string{"192.168.1.2"}) + router := gin.Default() + router.SetTrustedProxies([]string{"192.168.1.2"}) - router.GET("/", func(c *gin.Context) { - // If the client is 192.168.1.2, use the X-Forwarded-For - // header to deduce the original client IP from the trust- - // worthy parts of that header. - // Otherwise, simply return the direct client IP - fmt.Printf("ClientIP: %s\n", c.ClientIP()) - }) - router.Run() + router.GET("/", func(c *gin.Context) { + // If the client is 192.168.1.2, use the X-Forwarded-For + // header to deduce the original client IP from the trust- + // worthy parts of that header. + // Otherwise, simply return the direct client IP + fmt.Printf("ClientIP: %s\n", c.ClientIP()) + }) + router.Run() } ``` **Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` to skip TrustedProxies check, it has a higher priority than TrustedProxies. Look at the example below: + ```go import ( - "fmt" + "fmt" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) func main() { - router := gin.Default() - // Use predefined header gin.PlatformXXX - router.TrustedPlatform = gin.PlatformGoogleAppEngine - // Or set your own trusted request header for another trusted proxy service - // Don't set it to any suspect request header, it's unsafe - router.TrustedPlatform = "X-CDN-IP" + router := gin.Default() + // Use predefined header gin.PlatformXXX + router.TrustedPlatform = gin.PlatformGoogleAppEngine + // Or set your own trusted request header for another trusted proxy service + // Don't set it to any suspect request header, it's unsafe + router.TrustedPlatform = "X-CDN-IP" - router.GET("/", func(c *gin.Context) { - // If you set TrustedPlatform, ClientIP() will resolve the - // corresponding header and return IP directly - fmt.Printf("ClientIP: %s\n", c.ClientIP()) - }) - router.Run() + router.GET("/", func(c *gin.Context) { + // If you set TrustedPlatform, ClientIP() will resolve the + // corresponding header and return IP directly + fmt.Printf("ClientIP: %s\n", c.ClientIP()) + }) + router.Run() } ``` @@ -2290,22 +2307,22 @@ The `net/http/httptest` package is preferable way for HTTP testing. package main import ( - "net/http" + "net/http" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) func setupRouter() *gin.Engine { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - return r + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + return r } func main() { - r := setupRouter() - r.Run(":8080") + r := setupRouter() + r.Run(":8080") } ``` @@ -2315,22 +2332,22 @@ Test for code example above: package main import ( - "net/http" - "net/http/httptest" - "testing" + "net/http" + "net/http/httptest" + "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestPingRoute(t *testing.T) { - router := setupRouter() + router := setupRouter() - w := httptest.NewRecorder() - req, _ := http.NewRequest(http.MethodGet, "/ping", nil) - router.ServeHTTP(w, req) + w := httptest.NewRecorder() + req, _ := http.NewRequest(http.MethodGet, "/ping", nil) + router.ServeHTTP(w, req) - assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "pong", w.Body.String()) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "pong", w.Body.String()) } ``` @@ -2345,4 +2362,3 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. - From 8374ed2268e39c1033f6f3dc5c794b399285f164 Mon Sep 17 00:00:00 2001 From: Rainshaw Date: Tue, 2 Aug 2022 10:20:59 +0800 Subject: [PATCH 702/912] feat: add sonic json support (#3184) * feat: add sonic json support * fix: add blank line in readme --- .github/workflows/gin.yml | 2 +- Makefile | 2 +- README.md | 6 ++++++ go.mod | 5 +++++ go.sum | 23 +++++++++++++++++++++++ internal/json/json.go | 6 ++++-- internal/json/sonic.go | 27 +++++++++++++++++++++++++++ 7 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 internal/json/sonic.go diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 6dc787a..cb3a49a 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -29,7 +29,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] go: [1.15, 1.16, 1.17, 1.18] - test-tags: ['', nomsgpack] + test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest go-build: ~/.cache/go-build diff --git a/Makefile b/Makefile index 5d55b44..ebde4ee 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ TESTTAGS ?= "" test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ - $(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + $(GO) test $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ cat tmp.out; \ if grep -q "^--- FAIL" tmp.out; then \ rm tmp.out; \ diff --git a/README.md b/README.md index 1c315f8..8d7b5ec 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,12 @@ go build -tags=jsoniter . go build -tags=go_json . ``` +[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.) + +```sh +$ go build -tags="sonic avx" . +``` + ## Build without `MsgPack` rendering feature Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag. diff --git a/go.mod b/go.mod index 0b259e1..ed6402e 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( + github.com/bytedance/sonic v1.3.2 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 github.com/goccy/go-json v0.9.10 @@ -17,13 +18,17 @@ require ( ) require ( + github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.0.14 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect golang.org/x/text v0.3.6 // indirect diff --git a/go.sum b/go.sum index 640ed10..9b4fb91 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,8 @@ +github.com/bytedance/sonic v1.3.2 h1:xpJnWeCzu+XBfGBtNpk8jrMLZ+UduMEx0rAbHkFK5Cs= +github.com/bytedance/sonic v1.3.2/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a h1:lmGPzuocwDxoPAMr9h16zoJY/USZR9jIh99nrmKk1uI= +github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -20,6 +25,9 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= +github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -53,9 +61,23 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M= +github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc= +github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= +golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= @@ -86,3 +108,4 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/json/json.go b/internal/json/json.go index a26d7db..c5f3efc 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -2,8 +2,10 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build !jsoniter && !go_json -// +build !jsoniter,!go_json +//go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64) +// +build !jsoniter +// +build !go_json +// +build !sonic !avx !linux,!windows,!darwin !amd64 package json diff --git a/internal/json/sonic.go b/internal/json/sonic.go new file mode 100644 index 0000000..5a9ca4b --- /dev/null +++ b/internal/json/sonic.go @@ -0,0 +1,27 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build sonic && avx && (linux || windows || darwin) && amd64 +// +build sonic +// +build avx +// +build linux windows darwin +// +build amd64 + +package json + +import "github.com/bytedance/sonic" + +var ( + json = sonic.ConfigStd + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal + // MarshalIndent is exported by gin/json package. + MarshalIndent = json.MarshalIndent + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder +) From ad66d9d11a7d79d08be897bc617cda1e20f71855 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Aug 2022 15:28:30 +0800 Subject: [PATCH 703/912] chore(deps): bump google.golang.org/protobuf from 1.28.0 to 1.28.1 (#3262) Bumps [google.golang.org/protobuf](https://github.com/protocolbuffers/protobuf-go) from 1.28.0 to 1.28.1. - [Release notes](https://github.com/protocolbuffers/protobuf-go/releases) - [Changelog](https://github.com/protocolbuffers/protobuf-go/blob/master/release.bash) - [Commits](https://github.com/protocolbuffers/protobuf-go/compare/v1.28.0...v1.28.1) --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ed6402e..40f874e 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/stretchr/testify v1.8.0 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 - google.golang.org/protobuf v1.28.0 + google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 9b4fb91..30dc0ce 100644 --- a/go.sum +++ b/go.sum @@ -95,8 +95,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From 1b5ba251cfceec97020414b6c7dbc9fda697589a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 09:52:06 +0800 Subject: [PATCH 704/912] chore(deps): bump github.com/bytedance/sonic from 1.3.2 to 1.3.4 (#3273) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.3.2 to 1.3.4. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.3.2...v1.3.4) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 40f874e..924cefc 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.3.2 + github.com/bytedance/sonic v1.3.4 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 github.com/goccy/go-json v0.9.10 diff --git a/go.sum b/go.sum index 30dc0ce..58be7b0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/bytedance/sonic v1.3.2 h1:xpJnWeCzu+XBfGBtNpk8jrMLZ+UduMEx0rAbHkFK5Cs= -github.com/bytedance/sonic v1.3.2/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= +github.com/bytedance/sonic v1.3.4 h1:Pq+4YeIBh5VKMctAwqeiAsf18BCU24wZnwecwjIUCvU= +github.com/bytedance/sonic v1.3.4/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a h1:lmGPzuocwDxoPAMr9h16zoJY/USZR9jIh99nrmKk1uI= github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= @@ -17,6 +17,7 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc= github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= From b04917c53e310e3746ec90cd93106cda8c956211 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 15 Aug 2022 21:38:20 +0800 Subject: [PATCH 705/912] chore: upgrade golangci-lint and fix golangci-lint error (#3278) --- .github/workflows/gin.yml | 2 +- context.go | 70 ++++++++++++++++++++++----------------- context_test.go | 14 ++++---- errors.go | 9 ++--- errors_test.go | 6 ++-- ginS/gins.go | 3 +- logger_test.go | 6 ++-- middleware_test.go | 2 +- mode.go | 5 +-- path.go | 12 +++---- recovery.go | 2 +- routergroup.go | 3 +- 12 files changed, 74 insertions(+), 60 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index cb3a49a..cff8f4a 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -21,7 +21,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.2.0 with: - version: v1.45.0 + version: v1.48.0 args: --verbose test: needs: lint diff --git a/context.go b/context.go index 46bf113..f9489a7 100644 --- a/context.go +++ b/context.go @@ -153,9 +153,10 @@ func (c *Context) Handler() HandlerFunc { // FullPath returns a matched route full path. For not found routes // returns an empty string. -// router.GET("/user/:id", func(c *gin.Context) { -// c.FullPath() == "/user/:id" // true -// }) +// +// router.GET("/user/:id", func(c *gin.Context) { +// c.FullPath() == "/user/:id" // true +// }) func (c *Context) FullPath() string { return c.fullPath } @@ -382,10 +383,11 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) // Param returns the value of the URL param. // It is a shortcut for c.Params.ByName(key) -// router.GET("/user/:id", func(c *gin.Context) { -// // a GET request to /user/john -// id := c.Param("id") // id == "john" -// }) +// +// router.GET("/user/:id", func(c *gin.Context) { +// // a GET request to /user/john +// id := c.Param("id") // id == "john" +// }) func (c *Context) Param(key string) string { return c.Params.ByName(key) } @@ -402,11 +404,12 @@ func (c *Context) AddParam(key, value string) { // Query returns the keyed url query value if it exists, // otherwise it returns an empty string `("")`. // It is shortcut for `c.Request.URL.Query().Get(key)` -// GET /path?id=1234&name=Manu&value= -// c.Query("id") == "1234" -// c.Query("name") == "Manu" -// c.Query("value") == "" -// c.Query("wtf") == "" +// +// GET /path?id=1234&name=Manu&value= +// c.Query("id") == "1234" +// c.Query("name") == "Manu" +// c.Query("value") == "" +// c.Query("wtf") == "" func (c *Context) Query(key string) (value string) { value, _ = c.GetQuery(key) return @@ -415,10 +418,11 @@ func (c *Context) Query(key string) (value string) { // DefaultQuery returns the keyed url query value if it exists, // otherwise it returns the specified defaultValue string. // See: Query() and GetQuery() for further information. -// GET /?name=Manu&lastname= -// c.DefaultQuery("name", "unknown") == "Manu" -// c.DefaultQuery("id", "none") == "none" -// c.DefaultQuery("lastname", "none") == "" +// +// GET /?name=Manu&lastname= +// c.DefaultQuery("name", "unknown") == "Manu" +// c.DefaultQuery("id", "none") == "none" +// c.DefaultQuery("lastname", "none") == "" func (c *Context) DefaultQuery(key, defaultValue string) string { if value, ok := c.GetQuery(key); ok { return value @@ -430,10 +434,11 @@ func (c *Context) DefaultQuery(key, defaultValue string) string { // if it exists `(value, true)` (even when the value is an empty string), // otherwise it returns `("", false)`. // It is shortcut for `c.Request.URL.Query().Get(key)` -// GET /?name=Manu&lastname= -// ("Manu", true) == c.GetQuery("name") -// ("", false) == c.GetQuery("id") -// ("", true) == c.GetQuery("lastname") +// +// GET /?name=Manu&lastname= +// ("Manu", true) == c.GetQuery("name") +// ("", false) == c.GetQuery("id") +// ("", true) == c.GetQuery("lastname") func (c *Context) GetQuery(key string) (string, bool) { if values, ok := c.GetQueryArray(key); ok { return values[0], ok @@ -500,9 +505,10 @@ func (c *Context) DefaultPostForm(key, defaultValue string) string { // form or multipart form when it exists `(value, true)` (even when the value is an empty string), // otherwise it returns ("", false). // For example, during a PATCH request to update the user's email: -// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com" -// email= --> ("", true) := GetPostForm("email") // set email to "" -// --> ("", false) := GetPostForm("email") // do nothing with email +// +// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com" +// email= --> ("", true) := GetPostForm("email") // set email to "" +// --> ("", false) := GetPostForm("email") // do nothing with email func (c *Context) GetPostForm(key string) (string, bool) { if values, ok := c.GetPostFormArray(key); ok { return values[0], ok @@ -607,8 +613,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error // Bind checks the Method and Content-Type to select a binding engine automatically, // Depending on the "Content-Type" header different bindings are used, for example: -// "application/json" --> JSON binding -// "application/xml" --> XML binding +// +// "application/json" --> JSON binding +// "application/xml" --> XML binding +// // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. // It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. @@ -651,7 +659,7 @@ func (c *Context) BindHeader(obj any) error { // It will abort the request with HTTP 400 if any error occurs. func (c *Context) BindUri(obj any) error { if err := c.ShouldBindUri(obj); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck return err } return nil @@ -662,7 +670,7 @@ func (c *Context) BindUri(obj any) error { // See the binding package. func (c *Context) MustBindWith(obj any, b binding.Binding) error { if err := c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck return err } return nil @@ -670,8 +678,10 @@ func (c *Context) MustBindWith(obj any, b binding.Binding) error { // ShouldBind checks the Method and Content-Type to select a binding engine automatically, // Depending on the "Content-Type" header different bindings are used, for example: -// "application/json" --> JSON binding -// "application/xml" --> XML binding +// +// "application/json" --> JSON binding +// "application/xml" --> XML binding +// // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. // It decodes the json payload into the struct specified as a pointer. // Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid. @@ -1112,7 +1122,7 @@ func (c *Context) Negotiate(code int, config Negotiate) { c.TOML(code, data) default: - c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck + c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck } } diff --git a/context_test.go b/context_test.go index d09b0ae..b3e81c1 100644 --- a/context_test.go +++ b/context_test.go @@ -152,7 +152,7 @@ func TestContextReset(t *testing.T) { c.index = 2 c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()} c.Params = Params{Param{}} - c.Error(errors.New("test")) // nolint: errcheck + c.Error(errors.New("test")) //nolint: errcheck c.Set("foo", "bar") c.reset() @@ -1376,12 +1376,12 @@ func TestContextError(t *testing.T) { assert.Empty(t, c.Errors) firstErr := errors.New("first error") - c.Error(firstErr) // nolint: errcheck + c.Error(firstErr) //nolint: errcheck assert.Len(t, c.Errors, 1) assert.Equal(t, "Error #01: first error\n", c.Errors.String()) secondErr := errors.New("second error") - c.Error(&Error{ // nolint: errcheck + c.Error(&Error{ //nolint: errcheck Err: secondErr, Meta: "some data 2", Type: ErrorTypePublic, @@ -1403,13 +1403,13 @@ func TestContextError(t *testing.T) { t.Error("didn't panic") } }() - c.Error(nil) // nolint: errcheck + c.Error(nil) //nolint: errcheck } func TestContextTypedError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck - c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck + c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) //nolint: errcheck + c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) //nolint: errcheck for _, err := range c.Errors.ByType(ErrorTypePublic) { assert.Equal(t, ErrorTypePublic, err.Type) @@ -1424,7 +1424,7 @@ func TestContextAbortWithError(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck + c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") //nolint: errcheck assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, abortIndex, c.index) diff --git a/errors.go b/errors.go index 2853ce8..ca2bfc3 100644 --- a/errors.go +++ b/errors.go @@ -124,10 +124,11 @@ func (a errorMsgs) Last() *Error { // Errors returns an array with all the error messages. // Example: -// c.Error(errors.New("first")) -// c.Error(errors.New("second")) -// c.Error(errors.New("third")) -// c.Errors.Errors() // == []string{"first", "second", "third"} +// +// c.Error(errors.New("first")) +// c.Error(errors.New("second")) +// c.Error(errors.New("third")) +// c.Errors.Errors() // == []string{"first", "second", "third"} func (a errorMsgs) Errors() []string { if len(a) == 0 { return nil diff --git a/errors_test.go b/errors_test.go index 78d561c..f77a634 100644 --- a/errors_test.go +++ b/errors_test.go @@ -35,7 +35,7 @@ func TestError(t *testing.T) { jsonBytes, _ := json.Marshal(err) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) - err.SetMeta(H{ // nolint: errcheck + err.SetMeta(H{ //nolint: errcheck "status": "200", "data": "some data", }) @@ -45,7 +45,7 @@ func TestError(t *testing.T) { "data": "some data", }, err.JSON()) - err.SetMeta(H{ // nolint: errcheck + err.SetMeta(H{ //nolint: errcheck "error": "custom error", "status": "200", "data": "some data", @@ -60,7 +60,7 @@ func TestError(t *testing.T) { status string data string } - err.SetMeta(customError{status: "200", data: "other data"}) // nolint: errcheck + err.SetMeta(customError{status: "200", data: "other data"}) //nolint: errcheck assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON()) } diff --git a/ginS/gins.go b/ginS/gins.go index 1550b86..ea38c61 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -108,7 +108,8 @@ func StaticFile(relativePath, filepath string) gin.IRoutes { // of the Router's NotFound handler. // To use the operating system's file system implementation, // use : -// router.Static("/static", "/var/www") +// +// router.Static("/static", "/var/www") func Static(relativePath, root string) gin.IRoutes { return engine().Static(relativePath, root) } diff --git a/logger_test.go b/logger_test.go index fa0d9ce..7bc1137 100644 --- a/logger_test.go +++ b/logger_test.go @@ -358,13 +358,13 @@ func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) router.GET("/error", func(c *Context) { - c.Error(errors.New("this is an error")) // nolint: errcheck + c.Error(errors.New("this is an error")) //nolint: errcheck }) router.GET("/abort", func(c *Context) { - c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) // nolint: errcheck + c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) //nolint: errcheck }) router.GET("/print", func(c *Context) { - c.Error(errors.New("this is an error")) // nolint: errcheck + c.Error(errors.New("this is an error")) //nolint: errcheck c.String(http.StatusInternalServerError, "hola!") }) diff --git a/middleware_test.go b/middleware_test.go index a235fe9..acdf89c 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -211,7 +211,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { router := New() router.Use(func(context *Context) { signature += "A" - context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) // nolint: errcheck + context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) //nolint: errcheck }) router.Use(func(context *Context) { signature += "B" diff --git a/mode.go b/mode.go index 545fdaa..fd26d90 100644 --- a/mode.go +++ b/mode.go @@ -35,8 +35,9 @@ const ( // Note that both Logger and Recovery provides custom ways to configure their // output io.Writer. // To support coloring in Windows use: -// import "github.com/mattn/go-colorable" -// gin.DefaultWriter = colorable.NewColorableStdout() +// +// import "github.com/mattn/go-colorable" +// gin.DefaultWriter = colorable.NewColorableStdout() var DefaultWriter io.Writer = os.Stdout // DefaultErrorWriter is the default io.Writer used by Gin to debug errors diff --git a/path.go b/path.go index d42d6b9..82438c1 100644 --- a/path.go +++ b/path.go @@ -10,12 +10,12 @@ package gin // // The following rules are applied iteratively until no further processing can // be done: -// 1. Replace multiple slashes with a single slash. -// 2. Eliminate each . path name element (the current directory). -// 3. Eliminate each inner .. path name element (the parent directory) -// along with the non-.. element that precedes it. -// 4. Eliminate .. elements that begin a rooted path: -// that is, replace "/.." by "/" at the beginning of a path. +// 1. Replace multiple slashes with a single slash. +// 2. Eliminate each . path name element (the current directory). +// 3. Eliminate each inner .. path name element (the parent directory) +// along with the non-.. element that precedes it. +// 4. Eliminate .. elements that begin a rooted path: +// that is, replace "/.." by "/" at the beginning of a path. // // If the result of this process is an empty string, "/" is returned. func cleanPath(p string) string { diff --git a/recovery.go b/recovery.go index abb6451..05b30d9 100644 --- a/recovery.go +++ b/recovery.go @@ -91,7 +91,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } if brokenPipe { // If the connection is dead, we can't write a status to it. - c.Error(err.(error)) // nolint: errcheck + c.Error(err.(error)) //nolint: errcheck c.Abort() } else { handle(c, err) diff --git a/routergroup.go b/routergroup.go index 3c082d9..2474a81 100644 --- a/routergroup.go +++ b/routergroup.go @@ -182,7 +182,8 @@ func (group *RouterGroup) staticFileHandler(relativePath string, handler Handler // of the Router's NotFound handler. // To use the operating system's file system implementation, // use : -// router.Static("/static", "/var/www") +// +// router.Static("/static", "/var/www") func (group *RouterGroup) Static(relativePath, root string) IRoutes { return group.StaticFS(relativePath, Dir(root, false)) } From de17fb1a33743a587641d39bb6c79a5e14673da7 Mon Sep 17 00:00:00 2001 From: Aoang Date: Wed, 17 Aug 2022 07:14:19 +0800 Subject: [PATCH 706/912] Format with Go 1.19 formatter (#3277) * Format with Go 1.19 formatter This allows the GoDoc to take advantage of new markup syntax introduced in Go 1.19. This does not require that our minimum supported version be bumped to Go 1.19 since the pkgsite renders our godoc regardless of supported Go version. * Add Format check --- .github/workflows/gin.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index cff8f4a..14de5f7 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -68,6 +68,10 @@ jobs: uses: codecov/codecov-action@v3 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} + + - name: Format + if: matrix.go-version == '1.19.x' + run: diff -u <(echo -n) <(gofmt -d .) notification-gitter: needs: test runs-on: ubuntu-latest From 1c48977cca9e7a0a41c763376b6921a23cd06fe2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 07:14:59 +0800 Subject: [PATCH 707/912] chore(deps): bump github.com/mattn/go-isatty from 0.0.14 to 0.0.16 (#3281) Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.14 to 0.0.16. - [Release notes](https://github.com/mattn/go-isatty/releases) - [Commits](https://github.com/mattn/go-isatty/compare/v0.0.14...v0.0.16) --- updated-dependencies: - dependency-name: github.com/mattn/go-isatty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 924cefc..54cc552 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-playground/validator/v10 v10.10.0 github.com/goccy/go-json v0.9.10 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.14 + github.com/mattn/go-isatty v0.0.16 github.com/pelletier/go-toml/v2 v2.0.2 github.com/stretchr/testify v1.8.0 github.com/ugorji/go/codec v1.2.7 @@ -30,7 +30,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect - golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect golang.org/x/text v0.3.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 58be7b0..b88e4a8 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -85,9 +85,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxW golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= From fb13e822a4b48b872dd4538dd5bbd7a70767ca7a Mon Sep 17 00:00:00 2001 From: Alex <93376818+sashashura@users.noreply.github.com> Date: Wed, 31 Aug 2022 07:33:25 +0100 Subject: [PATCH 708/912] Update gin.yml (#3304) Signed-off-by: sashashura <93376818+sashashura@users.noreply.github.com> Signed-off-by: sashashura <93376818+sashashura@users.noreply.github.com> --- .github/workflows/gin.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 14de5f7..004f9b9 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -8,6 +8,9 @@ on: branches: - master +permissions: + contents: read + jobs: lint: runs-on: ubuntu-latest From 2ae61570499d8bb5eb05e46d22a3754cf2635e63 Mon Sep 17 00:00:00 2001 From: Dave Rolsky Date: Wed, 31 Aug 2022 01:34:33 -0500 Subject: [PATCH 709/912] Fix typos in RouterGroup method docs (#3302) There were a number of spots referring to a variable named "handle" that should be "handlers". --- routergroup.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/routergroup.go b/routergroup.go index 2474a81..8b877ed 100644 --- a/routergroup.go +++ b/routergroup.go @@ -106,37 +106,37 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha return group.handle(httpMethod, relativePath, handlers) } -// POST is a shortcut for router.Handle("POST", path, handle). +// POST is a shortcut for router.Handle("POST", path, handlers). func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodPost, relativePath, handlers) } -// GET is a shortcut for router.Handle("GET", path, handle). +// GET is a shortcut for router.Handle("GET", path, handlers). func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodGet, relativePath, handlers) } -// DELETE is a shortcut for router.Handle("DELETE", path, handle). +// DELETE is a shortcut for router.Handle("DELETE", path, handlers). func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodDelete, relativePath, handlers) } -// PATCH is a shortcut for router.Handle("PATCH", path, handle). +// PATCH is a shortcut for router.Handle("PATCH", path, handlers). func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodPatch, relativePath, handlers) } -// PUT is a shortcut for router.Handle("PUT", path, handle). +// PUT is a shortcut for router.Handle("PUT", path, handlers). func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodPut, relativePath, handlers) } -// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle). +// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handlers). func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodOptions, relativePath, handlers) } -// HEAD is a shortcut for router.Handle("HEAD", path, handle). +// HEAD is a shortcut for router.Handle("HEAD", path, handlers). func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle(http.MethodHead, relativePath, handlers) } From de1f142ed4b3279a24f2d1ab7993c6ae1a6c292a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 10:01:58 +0800 Subject: [PATCH 710/912] chore(deps): bump github.com/goccy/go-json from 0.9.10 to 0.9.11 (#3292) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.9.10 to 0.9.11. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.9.10...v0.9.11) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 54cc552..885ff0e 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/bytedance/sonic v1.3.4 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 - github.com/goccy/go-json v0.9.10 + github.com/goccy/go-json v0.9.11 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.16 github.com/pelletier/go-toml/v2 v2.0.2 diff --git a/go.sum b/go.sum index b88e4a8..8b56018 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc= -github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From 0128d74f340ed31065125a5ee6a481f2965c366d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 10:02:40 +0800 Subject: [PATCH 711/912] chore(deps): bump github.com/bytedance/sonic from 1.3.4 to 1.4.0 (#3293) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.3.4 to 1.4.0. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.3.4...v1.4.0) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 885ff0e..daf52e2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.3.4 + github.com/bytedance/sonic v1.4.0 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.10.0 github.com/goccy/go-json v0.9.11 diff --git a/go.sum b/go.sum index 8b56018..4d83273 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/bytedance/sonic v1.3.4 h1:Pq+4YeIBh5VKMctAwqeiAsf18BCU24wZnwecwjIUCvU= -github.com/bytedance/sonic v1.3.4/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= +github.com/bytedance/sonic v1.4.0 h1:d6vgPhwgHfpmEiz/9Fzea9fGzWY7RO1TQEySBiRwDLY= +github.com/bytedance/sonic v1.4.0/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a h1:lmGPzuocwDxoPAMr9h16zoJY/USZR9jIh99nrmKk1uI= github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= From 2c9e5fe47ae55defc55dae30a0e63192e4538bc2 Mon Sep 17 00:00:00 2001 From: Amir Hossein <77993374+Kamandlou@users.noreply.github.com> Date: Thu, 1 Sep 2022 06:51:27 +0430 Subject: [PATCH 712/912] rename variable because collide with the imported package name (#3298) * rename variable because collide with the imported package name * handle unhandled error in context_1.17_test.go --- context_1.17_test.go | 7 ++++++- gin_test.go | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/context_1.17_test.go b/context_1.17_test.go index 69c9786..23377ff 100644 --- a/context_1.17_test.go +++ b/context_1.17_test.go @@ -30,7 +30,12 @@ func (i interceptedWriter) WriteHeader(code int) { func TestContextFormFileFailed17(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) - mw.Close() + defer func(mw *multipart.Writer) { + err := mw.Close() + if err != nil { + assert.Error(t, err) + } + }(mw) c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) diff --git a/gin_test.go b/gin_test.go index 02f2324..4fac677 100644 --- a/gin_test.go +++ b/gin_test.go @@ -100,7 +100,7 @@ func TestH2c(t *testing.T) { url := "http://" + ln.Addr().String() + "/" - http := http.Client{ + httpClient := http.Client{ Transport: &http2.Transport{ AllowHTTP: true, DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { @@ -109,7 +109,7 @@ func TestH2c(t *testing.T) { }, } - res, err := http.Get(url) + res, err := httpClient.Get(url) if err != nil { fmt.Println(err) } From 814cd188eb0b0304dd3c37b698edc51ff46e24a2 Mon Sep 17 00:00:00 2001 From: Konstantin Runov <101004736+runebone@users.noreply.github.com> Date: Sun, 18 Sep 2022 16:59:57 +0300 Subject: [PATCH 713/912] FIX TYPO: Gin by default useR -> ... useS (#3324) --- routergroup.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 8b877ed..27308bc 100644 --- a/routergroup.go +++ b/routergroup.go @@ -161,7 +161,7 @@ func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { // StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead.. // router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false}) -// Gin by default user: gin.Dir() +// Gin by default uses: gin.Dir() func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes { return group.staticFileHandler(relativePath, func(c *Context) { c.FileFromFS(filepath, fs) @@ -189,7 +189,7 @@ func (group *RouterGroup) Static(relativePath, root string) IRoutes { } // StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead. -// Gin by default user: gin.Dir() +// Gin by default uses: gin.Dir() func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { panic("URL parameters can not be used when serving a static folder") From 78dad9d77d8c2d679dedea1fbef5fc8a54372efd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 14:44:55 +0800 Subject: [PATCH 714/912] chore(deps): bump github.com/go-playground/validator/v10 (#3330) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.10.0 to 10.11.1. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.10.0...v10.11.1) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 17 +++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index daf52e2..fa1db99 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,14 @@ go 1.18 require ( github.com/bytedance/sonic v1.4.0 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.10.0 + github.com/go-playground/validator/v10 v10.11.1 github.com/goccy/go-json v0.9.11 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.16 github.com/pelletier/go-toml/v2 v2.0.2 github.com/stretchr/testify v1.8.0 github.com/ugorji/go/codec v1.2.7 - golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -29,8 +29,8 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect - golang.org/x/text v0.3.6 // indirect + golang.org/x/text v0.3.7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4d83273..1c31d29 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -79,19 +79,20 @@ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 6fab4c373eaabe3425b08fe1a5f1484c93d07c2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 15:39:38 +0800 Subject: [PATCH 715/912] chore(deps): bump actions/setup-go from 2 to 3 (#3340) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 64ed8b2..654585b 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: 1.17 - From 6296175f70e21bd1c09efc424a2c14574904720d Mon Sep 17 00:00:00 2001 From: mstmdev Date: Wed, 12 Oct 2022 14:18:12 +0800 Subject: [PATCH 716/912] Fix the GO-2022-0969 and GO-2022-0288 vulnerabilities (#3333) --- .github/workflows/gin.yml | 2 +- go.mod | 2 +- go.sum | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 004f9b9..686d08a 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.15, 1.16, 1.17, 1.18] + go: [1.16, 1.17, 1.18] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest diff --git a/go.mod b/go.mod index fa1db99..1c64cf1 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.2 github.com/stretchr/testify v1.8.0 github.com/ugorji/go/codec v1.2.7 - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 + golang.org/x/net v0.0.0-20221004154528-8021a29435af google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 1c31d29..35bfd2e 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,9 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SX golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= +golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From fa58bff301a823ce25af800614d3f016b10ae586 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 16 Oct 2022 09:32:28 +0800 Subject: [PATCH 717/912] chore(dep): Changes minimum support go version to go1.16 (#3361) --- README.md | 2 +- debug.go | 4 ++-- debug_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d7b5ec..960c66d 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. You first need [Go](https://golang.org/) installed (**version 1.15+ is required**), then you can use the below Go command to install Gin. +1. You first need [Go](https://golang.org/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin. ```sh go get -u github.com/gin-gonic/gin diff --git a/debug.go b/debug.go index b9f8234..cbcedbc 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 15 +const ginSupportMinGoVer = 16 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.15+. + debugPrint(`[WARNING] Now Gin requires Go 1.16+. `) } diff --git a/debug_test.go b/debug_test.go index bf0e6ab..abe8b41 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.15+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.16+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } From 4c64f1c3859fa853659ff1db65a8f0fa67856ed9 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 16 Oct 2022 09:33:26 +0800 Subject: [PATCH 718/912] =?UTF-8?q?chore(go):=20Add=C2=A0support=20go=201.?= =?UTF-8?q?19=20(#3272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gin.yml | 2 +- context_1.17_test.go | 17 +++++++++++++++++ context_1.19_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 context_1.19_test.go diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 686d08a..18b36d0 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.16, 1.17, 1.18] + go: [1.16, 1.17, 1.18, 1.19] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest diff --git a/context_1.17_test.go b/context_1.17_test.go index 23377ff..0f8527f 100644 --- a/context_1.17_test.go +++ b/context_1.17_test.go @@ -12,6 +12,8 @@ import ( "mime/multipart" "net/http" "net/http/httptest" + "runtime" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -28,6 +30,9 @@ func (i interceptedWriter) WriteHeader(code int) { } func TestContextFormFileFailed17(t *testing.T) { + if !isGo117OrGo118() { + return + } buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) defer func(mw *multipart.Writer) { @@ -75,3 +80,15 @@ func TestInterceptedHeader(t *testing.T) { assert.Equal(t, "", w.Result().Header.Get("X-Test")) assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) } + +func isGo117OrGo118() bool { + version := strings.Split(runtime.Version()[2:], ".") + if len(version) >= 2 { + x := version[0] + y := version[1] + if x == "1" && (y == "17" || y == "18") { + return true + } + } + return false +} diff --git a/context_1.19_test.go b/context_1.19_test.go new file mode 100644 index 0000000..4b34ea2 --- /dev/null +++ b/context_1.19_test.go @@ -0,0 +1,31 @@ +// Copyright 2022 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go1.19 +// +build go1.19 + +package gin + +import ( + "bytes" + "mime/multipart" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContextFormFileFailed19(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + c.engine.MaxMultipartMemory = 8 << 20 + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) +} From 24a1d2adb9dba64d38426d76f2a4cf76123a4711 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Sun, 16 Oct 2022 11:41:14 +1000 Subject: [PATCH 719/912] fix(typo): spelling `covert` -> `convert` (#3325) --- binding/form_mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 98cebfe..540bbbb 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -19,7 +19,7 @@ import ( var ( errUnknownType = errors.New("unknown type") - // ErrConvertMapStringSlice can not covert to map[string][]string + // ErrConvertMapStringSlice can not convert to map[string][]string ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings") // ErrConvertToMapString can not convert to map[string]string From 45c758e2f9d36ee6a6e7d8b2131474970e41b12d Mon Sep 17 00:00:00 2001 From: Mohana sai krishna Kandula <73701479+mskKandula@users.noreply.github.com> Date: Sun, 16 Oct 2022 07:15:08 +0530 Subject: [PATCH 720/912] chore(file): Creates a directory named path (#3316) Co-authored-by: mohanak --- context.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/context.go b/context.go index f9489a7..b66b8ad 100644 --- a/context.go +++ b/context.go @@ -15,6 +15,7 @@ import ( "net/http" "net/url" "os" + "path/filepath" "strings" "sync" "time" @@ -601,6 +602,10 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error } defer src.Close() + if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil { + return err + } + out, err := os.Create(dst) if err != nil { return err From 33ab0fc155e68acbc7fd30281c0d9b2e6e091b7b Mon Sep 17 00:00:00 2001 From: hopehook Date: Sun, 16 Oct 2022 09:49:24 +0800 Subject: [PATCH 721/912] refactor(struct): Remove redundant type conversions (#3345) --- binding/binding_test.go | 8 ++------ binding/form_mapping_test.go | 8 ++++---- render/render_test.go | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index f099621..eae2780 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1107,9 +1107,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, struct { Idx int "form:\"idx\"" - }(struct { - Idx int "form:\"idx\"" - }{Idx: 123}), + }{Idx: 123}, obj.StructFoo) case "StructPointer": obj := FooStructForStructPointerType{} @@ -1118,9 +1116,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, struct { Name string "form:\"name\"" - }(struct { - Name string "form:\"name\"" - }{Name: "thinkerou"}), + }{Name: "thinkerou"}, *obj.StructPointerFoo) case "Map": obj := FooStructForMapType{} diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 78f4df0..93d6a92 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -114,7 +114,7 @@ func TestMappingPrivateField(t *testing.T) { } err := mappingByPtr(&s, formSource{"field": {"6"}}, "form") assert.NoError(t, err) - assert.Equal(t, int(0), s.f) + assert.Equal(t, 0, s.f) } func TestMappingUnknownFieldType(t *testing.T) { @@ -133,7 +133,7 @@ func TestMappingURI(t *testing.T) { } err := mapURI(&s, map[string][]string{"field": {"6"}}) assert.NoError(t, err) - assert.Equal(t, int(6), s.F) + assert.Equal(t, 6, s.F) } func TestMappingForm(t *testing.T) { @@ -142,7 +142,7 @@ func TestMappingForm(t *testing.T) { } err := mapForm(&s, map[string][]string{"field": {"6"}}) assert.NoError(t, err) - assert.Equal(t, int(6), s.F) + assert.Equal(t, 6, s.F) } func TestMapFormWithTag(t *testing.T) { @@ -151,7 +151,7 @@ func TestMapFormWithTag(t *testing.T) { } err := MapFormWithTag(&s, map[string][]string{"field": {"6"}}, "externalTag") assert.NoError(t, err) - assert.Equal(t, int(6), s.F) + assert.Equal(t, 6, s.F) } func TestMappingTime(t *testing.T) { diff --git a/render/render_test.go b/render/render_test.go index a13fff4..3509db3 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -173,7 +173,7 @@ func TestRenderAsciiJSON(t *testing.T) { assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() - data2 := float64(3.1415926) + data2 := 3.1415926 err = (AsciiJSON{data2}).Render(w2) assert.NoError(t, err) From 51aea73ba0f125f6cacc3b4b695efdf21d9c634f Mon Sep 17 00:00:00 2001 From: Jesse <1430482733@qq.com> Date: Thu, 20 Oct 2022 00:49:19 +0800 Subject: [PATCH 722/912] fix: modify interface check way (#3327) --- binding/default_validator.go | 2 +- context_test.go | 2 +- errors.go | 2 +- gin.go | 2 +- response_writer.go | 2 +- routergroup.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index c03afe7..e216b85 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -43,7 +43,7 @@ func (err SliceValidationError) Error() string { } } -var _ StructValidator = &defaultValidator{} +var _ StructValidator = (*defaultValidator)(nil) // ValidateStruct receives any kind of type, but only performed struct or pointer to struct type. func (v *defaultValidator) ValidateStruct(obj any) error { diff --git a/context_test.go b/context_test.go index b3e81c1..cc55cb0 100644 --- a/context_test.go +++ b/context_test.go @@ -30,7 +30,7 @@ import ( "google.golang.org/protobuf/proto" ) -var _ context.Context = &Context{} +var _ context.Context = (*Context)(nil) // Unit tests TODO // func (c *Context) File(filepath string) { diff --git a/errors.go b/errors.go index ca2bfc3..06b53c2 100644 --- a/errors.go +++ b/errors.go @@ -39,7 +39,7 @@ type Error struct { type errorMsgs []*Error -var _ error = &Error{} +var _ error = (*Error)(nil) // SetType sets the error's type. func (msg *Error) SetType(flags ErrorType) *Error { diff --git a/gin.go b/gin.go index f932429..a2e2e67 100644 --- a/gin.go +++ b/gin.go @@ -166,7 +166,7 @@ type Engine struct { trustedCIDRs []*net.IPNet } -var _ IRouter = &Engine{} +var _ IRouter = (*Engine)(nil) // New returns a new blank Engine instance without any middleware attached. // By default, the configuration is: diff --git a/response_writer.go b/response_writer.go index 77c7ed8..43e828d 100644 --- a/response_writer.go +++ b/response_writer.go @@ -49,7 +49,7 @@ type responseWriter struct { status int } -var _ ResponseWriter = &responseWriter{} +var _ ResponseWriter = (*responseWriter)(nil) func (w *responseWriter) reset(writer http.ResponseWriter) { w.ResponseWriter = writer diff --git a/routergroup.go b/routergroup.go index 27308bc..dfbdd7b 100644 --- a/routergroup.go +++ b/routergroup.go @@ -58,7 +58,7 @@ type RouterGroup struct { root bool } -var _ IRouter = &RouterGroup{} +var _ IRouter = (*RouterGroup)(nil) // Use adds middleware to the group, see example code in GitHub. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { From 8b9c55e8b059a1ffcc69e3b5dc0b9d583958811f Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sun, 6 Nov 2022 17:02:40 +0800 Subject: [PATCH 723/912] fix(route): redirectSlash bug (#3227) fixes https://github.com/gin-gonic/gin/issues/2959 fixes https://github.com/gin-gonic/gin/issues/2282 fixes https://github.com/gin-gonic/gin/issues/2211 --- tree.go | 9 ++++++++- tree_test.go | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 956bf4d..3f34b8e 100644 --- a/tree.go +++ b/tree.go @@ -107,7 +107,8 @@ func countSections(path string) uint16 { type nodeType uint8 const ( - root nodeType = iota + 1 + static nodeType = iota + root param catchAll ) @@ -173,6 +174,7 @@ walk: child := node{ path: n.path[i:], wildChild: n.wildChild, + nType: static, indices: n.indices, children: n.children, handlers: n.handlers, @@ -604,6 +606,11 @@ walk: // Outer loop for walking the tree return } + if path == "/" && n.nType == static { + value.tsr = true + return + } + // No handle found. Check if a handle for this path + a // trailing slash exists for trailing slash recommendation for i, c := range []byte(n.indices) { diff --git a/tree_test.go b/tree_test.go index 085b580..2005738 100644 --- a/tree_test.go +++ b/tree_test.go @@ -684,6 +684,26 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { } } +func TestRedirectTrailingSlash(t *testing.T) { + var data = []struct { + path string + }{ + {"/hello/:name"}, + {"/hello/:name/123"}, + {"/hello/:name/234"}, + } + + node := &node{} + for _, item := range data { + node.addRoute(item.path, fakeHandler("test")) + } + + value := node.getValue("/hello/abx/", nil, getSkippedNodes(), false) + if value.tsr != true { + t.Fatalf("want true, is false") + } +} + func TestTreeFindCaseInsensitivePath(t *testing.T) { tree := &node{} From 971fe21876e491f2597a390522adc849272e3bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=93=88=E5=93=88?= <31426858+wanghaha-dev@users.noreply.github.com> Date: Sun, 6 Nov 2022 17:05:10 +0800 Subject: [PATCH 724/912] docs(comment): Modify comment syntax error (#3389) --- CHANGELOG.md | 2 +- README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc51a8..a682c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,7 +113,7 @@ * chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378)) * Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387)) * update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321)) -* remove a unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391)) +* remove an unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391)) * Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389)) * Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322)) * binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450)) diff --git a/README.md b/README.md index 960c66d..760bde0 100644 --- a/README.md +++ b/README.md @@ -293,7 +293,7 @@ func main() { router := gin.Default() // Query string parameters are parsed using the existing underlying request object. - // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe + // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") @@ -693,7 +693,7 @@ Also, Gin provides two sets of methods for binding: 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. +You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has an empty value when binding, an error will be returned. ```go // Binding from JSON @@ -2096,7 +2096,7 @@ func validate(obj interface{}) error { } // Now we can do this!!! -// FormA is a external type that we can't modify it's tag +// FormA is an external type that we can't modify it's tag type FormA struct { FieldA string `url:"field_a"` } From 55e27f12465e058058180280d5f0bdc473eb3302 Mon Sep 17 00:00:00 2001 From: RoCry Date: Sun, 6 Nov 2022 17:08:11 +0800 Subject: [PATCH 725/912] fix(engine): missing route params for CreateTestContext (#2778) (#2803) --- context_test.go | 16 +++++++++++++++- gin.go | 6 +++--- test_helpers.go | 10 +++++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/context_test.go b/context_test.go index cc55cb0..85e0a61 100644 --- a/context_test.go +++ b/context_test.go @@ -146,7 +146,7 @@ func TestSaveUploadedCreateFailed(t *testing.T) { func TestContextReset(t *testing.T) { router := New() - c := router.allocateContext() + c := router.allocateContext(0) assert.Equal(t, c.engine, router) c.index = 2 @@ -2354,3 +2354,17 @@ func TestContextAddParam(t *testing.T) { assert.Equal(t, ok, true) assert.Equal(t, value, v) } + +func TestCreateTestContextWithRouteParams(t *testing.T) { + w := httptest.NewRecorder() + engine := New() + engine.GET("/:action/:name", func(ctx *Context) { + ctx.String(http.StatusOK, "%s %s", ctx.Param("action"), ctx.Param("name")) + }) + c := CreateTestContextOnly(w, engine) + c.Request, _ = http.NewRequest(http.MethodGet, "/hello/gin", nil) + engine.HandleContext(c) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "hello gin", w.Body.String()) +} diff --git a/gin.go b/gin.go index a2e2e67..35159d0 100644 --- a/gin.go +++ b/gin.go @@ -203,7 +203,7 @@ func New() *Engine { } engine.RouterGroup.engine = engine engine.pool.New = func() any { - return engine.allocateContext() + return engine.allocateContext(engine.maxParams) } return engine } @@ -225,8 +225,8 @@ func (engine *Engine) Handler() http.Handler { return h2c.NewHandler(engine, h2s) } -func (engine *Engine) allocateContext() *Context { - v := make(Params, 0, engine.maxParams) +func (engine *Engine) allocateContext(maxParams uint16) *Context { + v := make(Params, 0, maxParams) skippedNodes := make([]skippedNode, 0, engine.maxSections) return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes} } diff --git a/test_helpers.go b/test_helpers.go index b3be93b..7508c5c 100644 --- a/test_helpers.go +++ b/test_helpers.go @@ -9,7 +9,15 @@ import "net/http" // CreateTestContext returns a fresh engine and context for testing purposes func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) { r = New() - c = r.allocateContext() + c = r.allocateContext(0) + c.reset() + c.writermem.reset(w) + return +} + +// CreateTestContextOnly returns a fresh context base on the engine for testing purposes +func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) { + c = r.allocateContext(r.maxParams) c.reset() c.writermem.reset(w) return From 3a6865ac03871578cd68dcf2ebe64205b325c1b1 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 6 Nov 2022 17:09:25 +0800 Subject: [PATCH 726/912] docs(readme): The krakend is rename to lura (#3377) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 760bde0..8a6b262 100644 --- a/README.md +++ b/README.md @@ -2364,7 +2364,7 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. * [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. +* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. From 8edb7a71a17061bbaa85db53bb8ba6daad3c3aa8 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 6 Nov 2022 17:10:33 +0800 Subject: [PATCH 727/912] docs(readme): Update some go website links (#3376) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8a6b262..6445e97 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. You first need [Go](https://golang.org/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin. +1. You first need [Go](https://go.dev/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin. ```sh go get -u github.com/gin-gonic/gin @@ -678,7 +678,7 @@ func main() { To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz). -Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). +Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. @@ -1820,7 +1820,7 @@ Alternatives: #### Manually -In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). +In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). ```go // +build go1.8 @@ -2116,7 +2116,7 @@ func ListHandler(s *Service) func(ctx *gin.Context) { ### http2 server push -http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. +http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information. ```go package main From aefae309a4fc197ce5d57cd8391562b6d2a63a95 Mon Sep 17 00:00:00 2001 From: jessetang <1430482733@qq.com> Date: Sun, 6 Nov 2022 17:12:11 +0800 Subject: [PATCH 728/912] fix: test fmt.Println replace t.Error (#3328) --- gin_test.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/gin_test.go b/gin_test.go index 4fac677..5ab2430 100644 --- a/gin_test.go +++ b/gin_test.go @@ -73,7 +73,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -83,7 +83,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { func TestH2c(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { - fmt.Println(err) + t.Error(err) } r := Default() r.UseH2C = true @@ -93,7 +93,7 @@ func TestH2c(t *testing.T) { go func() { err := http.Serve(ln, r.Handler()) if err != nil { - fmt.Println(err) + t.Log(err) } }() defer ln.Close() @@ -111,7 +111,7 @@ func TestH2c(t *testing.T) { res, err := httpClient.Get(url) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -131,7 +131,7 @@ func TestLoadHTMLGlobTestMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -151,7 +151,7 @@ func TestLoadHTMLGlobReleaseMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -178,7 +178,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { client := &http.Client{Transport: tr} res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -198,7 +198,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -229,7 +229,7 @@ func TestLoadHTMLFilesTestMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -249,7 +249,7 @@ func TestLoadHTMLFilesDebugMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -269,7 +269,7 @@ func TestLoadHTMLFilesReleaseMode(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -296,7 +296,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { client := &http.Client{Transport: tr} res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) @@ -316,7 +316,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) if err != nil { - fmt.Println(err) + t.Error(err) } resp, _ := ioutil.ReadAll(res.Body) From c4b3c2c23a5ab25a80e8648f504065922631593e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Nov 2022 19:53:08 +0800 Subject: [PATCH 729/912] chore(deps): bump github.com/stretchr/testify from 1.8.0 to 1.8.1 (#3373) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 1c64cf1..ebc1bd1 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.16 github.com/pelletier/go-toml/v2 v2.0.2 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20221004154528-8021a29435af google.golang.org/protobuf v1.28.1 diff --git a/go.sum b/go.sum index 35bfd2e..acdf805 100644 --- a/go.sum +++ b/go.sum @@ -55,13 +55,15 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M= github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= From b682b8a54e165088efa71d8b941e7ad28cde348b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Nov 2022 19:53:33 +0800 Subject: [PATCH 730/912] chore(deps): bump golangci/golangci-lint-action from 3.2.0 to 3.3.0 (#3372) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.2.0...v3.3.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 18b36d0..45ae932 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.2.0 + uses: golangci/golangci-lint-action@v3.3.0 with: version: v1.48.0 args: --verbose From 212267d6716324536be15512c0b6ed080794b798 Mon Sep 17 00:00:00 2001 From: lgbgbl <65756378+lgbgbl@users.noreply.github.com> Date: Tue, 8 Nov 2022 19:54:48 +0800 Subject: [PATCH 731/912] fix: fix typo in comment (#3371) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index b66b8ad..ac9db17 100644 --- a/context.go +++ b/context.go @@ -558,7 +558,7 @@ func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { return c.get(c.formCache, key) } -// get is an internal method and returns a map which satisfy conditions. +// get is an internal method and returns a map which satisfies conditions. func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) { dicts := make(map[string]string) exist := false From a0acf1df2814fcd828cb2d7128f2f4e2136d3fac Mon Sep 17 00:00:00 2001 From: mstmdev Date: Wed, 9 Nov 2022 14:50:46 +0800 Subject: [PATCH 732/912] docs(readme): Using the embed package as a recommended example that build a single binary with templates (#3379) --- README.md | 72 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 6445e97..48a0a13 100644 --- a/README.md +++ b/README.md @@ -1884,48 +1884,56 @@ func main() { ### 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 +You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package. ```go +package main + +import ( + "embed" + "html/template" + "net/http" + + "github.com/gin-gonic/gin" +) + +//go:embed assets/* templates/* +var f embed.FS + func main() { - r := gin.New() + router := gin.Default() + templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) + router.SetHTMLTemplate(templ) - t, err := loadTemplate() - if err != nil { - panic(err) - } - r.SetHTMLTemplate(t) + // example: /public/assets/images/example.png + router.StaticFS("/public", http.FS(f)) - r.GET("/", func(c *gin.Context) { - c.HTML(http.StatusOK, "/html/index.tmpl",nil) + router.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.tmpl", gin.H{ + "title": "Main website", + }) }) - r.Run(":8080") -} -// loadTemplate loads templates embedded by go-assets-builder -func loadTemplate() (*template.Template, error) { - t := template.New("") - for name, file := range Assets.Files { - defer file.Close() - if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { - continue - } - h, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - t, err = t.New(name).Parse(string(h)) - if err != nil { - return nil, err - } - } - return t, nil + router.GET("/foo", func(c *gin.Context) { + c.HTML(http.StatusOK, "bar.tmpl", gin.H{ + "title": "Foo website", + }) + }) + + router.GET("favicon.ico", func(c *gin.Context) { + file, _ := f.ReadFile("assets/favicon.ico") + c.Data( + http.StatusOK, + "image/x-icon", + file, + ) + }) + + router.Run(":8080") } ``` -See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary` directory. +See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory. ### Bind form-data request with custom struct From 234a1d33f7b329a6d701a7b249167f72de57c901 Mon Sep 17 00:00:00 2001 From: gobai <38973236+go-bai@users.noreply.github.com> Date: Thu, 17 Nov 2022 22:34:37 +0800 Subject: [PATCH 733/912] docs(readme): Modify sample code bugs (#3394) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 48a0a13..65492e5 100644 --- a/README.md +++ b/README.md @@ -1854,7 +1854,7 @@ func main() { // Initializing the server in a goroutine so that // it won't block the graceful shutdown handling below go func() { - if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Printf("listen: %s\n", err) } }() From 6150c488e73518f119cfed53094d6179a0d33bf7 Mon Sep 17 00:00:00 2001 From: Qt Date: Thu, 17 Nov 2022 22:35:55 +0800 Subject: [PATCH 734/912] remove deprecated of package io/ioutil (#3395) --- binding/binding_test.go | 9 ++++----- binding/multipart_form_mapping_test.go | 4 ++-- binding/protobuf.go | 4 ++-- context.go | 5 ++--- gin_integration_test.go | 4 ++-- gin_test.go | 24 ++++++++++++------------ recovery.go | 3 +-- routes_test.go | 5 ++--- 8 files changed, 27 insertions(+), 31 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index eae2780..9af4f88 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -9,7 +9,6 @@ import ( "encoding/json" "errors" "io" - "io/ioutil" "mime/multipart" "net/http" "os" @@ -656,12 +655,12 @@ func TestBindingFormFilesMultipart(t *testing.T) { // file from os f, _ := os.Open("form.go") defer f.Close() - fileActual, _ := ioutil.ReadAll(f) + fileActual, _ := io.ReadAll(f) // file from multipart mf, _ := obj.File.Open() defer mf.Close() - fileExpect, _ := ioutil.ReadAll(mf) + fileExpect, _ := io.ReadAll(mf) assert.Equal(t, FormMultipart.Name(), "multipart/form-data") assert.Equal(t, obj.Foo, "bar") @@ -1347,13 +1346,13 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body obj := protoexample.Test{} req := requestWithBody("POST", path, body) - req.Body = ioutil.NopCloser(&hook{}) + req.Body = io.NopCloser(&hook{}) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.Error(t, err) invalidobj := FooStruct{} - req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`)) + req.Body = io.NopCloser(strings.NewReader(`{"msg":"hello"}`)) req.Header.Add("Content-Type", MIMEPROTOBUF) err = b.Bind(req, &invalidobj) assert.Error(t, err) diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go index 9932860..4e97c0f 100644 --- a/binding/multipart_form_mapping_test.go +++ b/binding/multipart_form_mapping_test.go @@ -6,7 +6,7 @@ package binding import ( "bytes" - "io/ioutil" + "io" "mime/multipart" "net/http" "testing" @@ -129,7 +129,7 @@ func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file test fl, err := fh.Open() assert.NoError(t, err) - body, err := ioutil.ReadAll(fl) + body, err := io.ReadAll(fl) assert.NoError(t, err) assert.Equal(t, string(file.Content), string(body)) diff --git a/binding/protobuf.go b/binding/protobuf.go index 44f2fdb..57721fc 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -6,7 +6,7 @@ package binding import ( "errors" - "io/ioutil" + "io" "net/http" "google.golang.org/protobuf/proto" @@ -19,7 +19,7 @@ func (protobufBinding) Name() string { } func (b protobufBinding) Bind(req *http.Request, obj any) error { - buf, err := ioutil.ReadAll(req.Body) + buf, err := io.ReadAll(req.Body) if err != nil { return err } diff --git a/context.go b/context.go index ac9db17..c41c71e 100644 --- a/context.go +++ b/context.go @@ -7,7 +7,6 @@ package gin import ( "errors" "io" - "io/ioutil" "log" "math" "mime/multipart" @@ -753,7 +752,7 @@ func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error } } if body == nil { - body, err = ioutil.ReadAll(c.Request.Body) + body, err = io.ReadAll(c.Request.Body) if err != nil { return err } @@ -872,7 +871,7 @@ func (c *Context) GetHeader(key string) string { // GetRawData returns stream data. func (c *Context) GetRawData() ([]byte, error) { - return ioutil.ReadAll(c.Request.Body) + return io.ReadAll(c.Request.Body) } // SetSameSite with cookie diff --git a/gin_integration_test.go b/gin_integration_test.go index b0532a2..02b9622 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -9,7 +9,7 @@ import ( "crypto/tls" "fmt" "html/template" - "io/ioutil" + "io" "net" "net/http" "net/http/httptest" @@ -43,7 +43,7 @@ func testRequest(t *testing.T, params ...string) { assert.NoError(t, err) defer resp.Body.Close() - body, ioerr := ioutil.ReadAll(resp.Body) + body, ioerr := io.ReadAll(resp.Body) assert.NoError(t, ioerr) var responseStatus = "200 OK" diff --git a/gin_test.go b/gin_test.go index 5ab2430..8825ac7 100644 --- a/gin_test.go +++ b/gin_test.go @@ -8,7 +8,7 @@ import ( "crypto/tls" "fmt" "html/template" - "io/ioutil" + "io" "net" "net/http" "net/http/httptest" @@ -76,7 +76,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -114,7 +114,7 @@ func TestH2c(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -134,7 +134,7 @@ func TestLoadHTMLGlobTestMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -154,7 +154,7 @@ func TestLoadHTMLGlobReleaseMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -181,7 +181,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -201,7 +201,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01", string(resp)) } @@ -232,7 +232,7 @@ func TestLoadHTMLFilesTestMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -252,7 +252,7 @@ func TestLoadHTMLFilesDebugMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -272,7 +272,7 @@ func TestLoadHTMLFilesReleaseMode(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -299,7 +299,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "

Hello world

", string(resp)) } @@ -319,7 +319,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { t.Error(err) } - resp, _ := ioutil.ReadAll(res.Body) + resp, _ := io.ReadAll(res.Body) assert.Equal(t, "Date: 2017/07/01", string(resp)) } diff --git a/recovery.go b/recovery.go index 05b30d9..3a90f25 100644 --- a/recovery.go +++ b/recovery.go @@ -9,7 +9,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "log" "net" "net/http" @@ -121,7 +120,7 @@ func stack(skip int) []byte { // Print this much at least. If we can't find the source, it won't show. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) if file != lastFile { - data, err := ioutil.ReadFile(file) + data, err := os.ReadFile(file) if err != nil { continue } diff --git a/routes_test.go b/routes_test.go index d7034b2..cd8cf14 100644 --- a/routes_test.go +++ b/routes_test.go @@ -6,7 +6,6 @@ package gin import ( "fmt" - "io/ioutil" "net/http" "net/http/httptest" "os" @@ -294,7 +293,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { func TestRouteStaticFile(t *testing.T) { // SETUP file testRoot, _ := os.Getwd() - f, err := ioutil.TempFile(testRoot, "") + f, err := os.CreateTemp(testRoot, "") if err != nil { t.Error(err) } @@ -329,7 +328,7 @@ func TestRouteStaticFile(t *testing.T) { func TestRouteStaticFileFS(t *testing.T) { // SETUP file testRoot, _ := os.Getwd() - f, err := ioutil.TempFile(testRoot, "") + f, err := os.CreateTemp(testRoot, "") if err != nil { t.Error(err) } From c629689591bf3f50650292f382f16ec9b6952904 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Thu, 17 Nov 2022 22:37:50 +0800 Subject: [PATCH 735/912] docs(readme): Add the TOML rendering example (#3400) --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 65492e5..103a53c 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind Header](#bind-header) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) + - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering) - [SecureJSON](#securejson) - [JSONP](#jsonp) - [AsciiJSON](#asciijson) @@ -1114,7 +1114,7 @@ Test it with: curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile ``` -### XML, JSON, YAML and ProtoBuf rendering +### XML, JSON, YAML, TOML and ProtoBuf rendering ```go func main() { @@ -1148,6 +1148,10 @@ func main() { c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) + r.GET("/someTOML", func(c *gin.Context) { + c.TOML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) + }) + r.GET("/someProtoBuf", func(c *gin.Context) { reps := []int64{int64(1), int64(2)} label := "test" From 8fe209a447426233f55093af1d51d3964ca10654 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Nov 2022 22:38:19 +0800 Subject: [PATCH 736/912] chore(deps): bump golangci/golangci-lint-action from 3.3.0 to 3.3.1 (#3399) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 45ae932..7a4e61c 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.3.0 + uses: golangci/golangci-lint-action@v3.3.1 with: version: v1.48.0 args: --verbose From 80cd679c43a3d6ed03957b6a3614f97d0af0751c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:34:18 +0800 Subject: [PATCH 737/912] chore(deps): bump github.com/pelletier/go-toml/v2 from 2.0.2 to 2.0.6 (#3408) Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.2 to 2.0.6. - [Release notes](https://github.com/pelletier/go-toml/releases) - [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml) - [Commits](https://github.com/pelletier/go-toml/compare/v2.0.2...v2.0.6) --- updated-dependencies: - dependency-name: github.com/pelletier/go-toml/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index ebc1bd1..200a440 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/goccy/go-json v0.9.11 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.16 - github.com/pelletier/go-toml/v2 v2.0.2 + github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.0.0-20221004154528-8021a29435af diff --git a/go.sum b/go.sum index acdf805..574e4a9 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= -github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -60,7 +60,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= From cc367f9125516313a4b8d2da1eed5527b5420dbc Mon Sep 17 00:00:00 2001 From: Cookiery <33125275+Cookiery@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:15:31 +0800 Subject: [PATCH 738/912] docs(context): #3369 modify the annotation about Context.Param() (#3414) --- context.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index c41c71e..737e4d7 100644 --- a/context.go +++ b/context.go @@ -386,7 +386,9 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) // // router.GET("/user/:id", func(c *gin.Context) { // // a GET request to /user/john -// id := c.Param("id") // id == "john" +// id := c.Param("id") // id == "/john" +// // a GET request to /user/john/ +// id := c.Param("id") // id == "/john/" // }) func (c *Context) Param(key string) string { return c.Params.ByName(key) From 483ac2a63bff7e1104fbe236dacff774c09bc24d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 14:43:06 +0800 Subject: [PATCH 739/912] chore(deps): bump goreleaser/goreleaser-action from 3 to 4 (#3441) Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 3 to 4. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v3...v4) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 654585b..3af3a45 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -24,7 +24,7 @@ jobs: go-version: 1.17 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v3 + uses: goreleaser/goreleaser-action@v4 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser From f551d7d8c2930c0140781a284f4372490c365a43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 14:43:42 +0800 Subject: [PATCH 740/912] chore(deps): bump github.com/bytedance/sonic from 1.4.0 to 1.6.0 (#3442) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.4.0 to 1.6.0. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.4.0...v1.6.0) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 19 +++++-------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 200a440..1929c95 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.4.0 + github.com/bytedance/sonic v1.6.0 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.1 github.com/goccy/go-json v0.9.11 @@ -18,7 +18,7 @@ require ( ) require ( - github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect diff --git a/go.sum b/go.sum index 574e4a9..054058a 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,9 @@ -github.com/bytedance/sonic v1.4.0 h1:d6vgPhwgHfpmEiz/9Fzea9fGzWY7RO1TQEySBiRwDLY= -github.com/bytedance/sonic v1.4.0/go.mod h1:V973WhNhGmvHxW6nQmsHEfHaoU9F3zTF+93rH03hcUQ= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.6.0 h1:j90DM/Ss1bmySEQYL2U4jRsUjJ+chASzCCZYxohJR60= +github.com/bytedance/sonic v1.6.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a h1:lmGPzuocwDxoPAMr9h16zoJY/USZR9jIh99nrmKk1uI= -github.com/chenzhuoyu/base64x v0.0.0-20220526154910-8bf9453eb81a/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -17,7 +18,6 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -63,15 +63,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M= -github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc= -github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= From d4caeee7c7672dca05e5033e9e30204b52d398e6 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Wed, 21 Dec 2022 14:44:36 +0800 Subject: [PATCH 741/912] Fix the GO-2022-1144 vulnerability (#3432) --- go.mod | 6 +++--- go.sum | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 1929c95..7a355e9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.7 - golang.org/x/net v0.0.0-20221004154528-8021a29435af + golang.org/x/net v0.4.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -30,7 +30,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 054058a..516c053 100644 --- a/go.sum +++ b/go.sum @@ -74,18 +74,20 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4= -golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 2285aa5430fb5e68bb55a89009fc4f5dd261b466 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 21 Dec 2022 15:02:00 +0800 Subject: [PATCH 742/912] docs(readme): release v1.8.2 version (#3420) * docs(readme): release v1.8.2 version * Update CHANGELOG.md --- CHANGELOG.md | 11 +++++++++++ version.go | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a682c8c..2ab5617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Gin ChangeLog +## Gin v1.8.2 + +### Bugs + +* fix(route): redirectSlash bug ([#3227]((https://github.com/gin-gonic/gin/pull/3227))) +* fix(engine): missing route params for CreateTestContext ([#2778]((https://github.com/gin-gonic/gin/pull/2778))) ([#2803]((https://github.com/gin-gonic/gin/pull/2803))) + +### Security + +* Fix the GO-2022-1144 vulnerability ([#3432]((https://github.com/gin-gonic/gin/pull/3432))) + ## Gin v1.8.1 ### ENHANCEMENTS diff --git a/version.go b/version.go index 632ca7d..37e27f2 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.8.1" +const Version = "v1.8.2" From 297b664cf8bbb3b9434e473cf976c98f00528ff7 Mon Sep 17 00:00:00 2001 From: lgbgbl <65756378+lgbgbl@users.noreply.github.com> Date: Thu, 22 Dec 2022 23:17:19 +0800 Subject: [PATCH 743/912] refactor: avoid calling strings.ToLower twice (#3433) --- recovery.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 3a90f25..2955c03 100644 --- a/recovery.go +++ b/recovery.go @@ -62,7 +62,9 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { if ne, ok := err.(*net.OpError); ok { var se *os.SyscallError if errors.As(ne, &se) { - if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + seStr := strings.ToLower(se.Error()) + if strings.Contains(seStr, "broken pipe") || + strings.Contains(seStr, "connection reset by peer") { brokenPipe = true } } From e868fd1d3d350d7186efcdf71f24f56d8e2b8408 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Thu, 22 Dec 2022 23:18:47 +0800 Subject: [PATCH 744/912] test(TOML): Add some tests for the TOML render (#3401) --- render/render_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/render/render_test.go b/render/render_test.go index 3509db3..c5c5375 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -8,6 +8,7 @@ import ( "encoding/xml" "errors" "html/template" + "net" "net/http" "net/http/httptest" "strconv" @@ -254,6 +255,27 @@ func TestRenderYAMLFail(t *testing.T) { assert.Error(t, err) } +func TestRenderTOML(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]any{ + "foo": "bar", + "html": "", + } + (TOML{data}).WriteContentType(w) + assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) + + err := (TOML{data}).Render(w) + assert.NoError(t, err) + assert.Equal(t, "foo = 'bar'\nhtml = ''\n", w.Body.String()) + assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) +} + +func TestRenderTOMLFail(t *testing.T) { + w := httptest.NewRecorder() + err := (TOML{net.IPv4bcast}).Render(w) + assert.Error(t, err) +} + // test Protobuf rendering func TestRenderProtoBuf(t *testing.T) { w := httptest.NewRecorder() From 8659ab573cf7d26b2fa2a41e90075d84606188f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Dec 2022 13:49:44 +0800 Subject: [PATCH 745/912] chore(deps): bump github.com/goccy/go-json from 0.9.11 to 0.10.0 (#3424) Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.9.11 to 0.10.0. - [Release notes](https://github.com/goccy/go-json/releases) - [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md) - [Commits](https://github.com/goccy/go-json/compare/v0.9.11...v0.10.0) --- updated-dependencies: - dependency-name: github.com/goccy/go-json dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7a355e9..e969833 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/bytedance/sonic v1.6.0 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.1 - github.com/goccy/go-json v0.9.11 + github.com/goccy/go-json v0.10.0 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.16 github.com/pelletier/go-toml/v2 v2.0.6 diff --git a/go.sum b/go.sum index 516c053..6d8df64 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= +github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= From 82e1c53cc0a3b0bdb7baab6b95d05a7ec796f7a2 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 2 Jan 2023 10:40:25 +0800 Subject: [PATCH 746/912] docs(readme): move more example to docs/doc.md (#3449) --- README.md | 2352 ++------------------------------------------------- docs/doc.md | 2246 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2321 insertions(+), 2277 deletions(-) create mode 100644 docs/doc.md diff --git a/README.md b/README.md index 103a53c..eccf814 100644 --- a/README.md +++ b/README.md @@ -12,106 +12,47 @@ [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) [![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/gin-gonic/gin)](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin) -Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. +Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. -## Contents +**The key features of Gin are:** -- [Gin Web Framework](#gin-web-framework) - - [Contents](#contents) - - [Installation](#installation) - - [Quick start](#quick-start) - - [Benchmarks](#benchmarks) - - [Gin v1. stable](#gin-v1-stable) - - [Build with json replacement](#build-with-json-replacement) - - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature) - - [API Examples](#api-examples) - - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) - - [Parameters in path](#parameters-in-path) - - [Querystring parameters](#querystring-parameters) - - [Multipart/Urlencoded Form](#multiparturlencoded-form) - - [Another example: query + post form](#another-example-query--post-form) - - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) - - [Upload files](#upload-files) - - [Single file](#single-file) - - [Multiple files](#multiple-files) - - [Grouping routes](#grouping-routes) - - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - - [Using middleware](#using-middleware) - - [Custom Recovery behavior](#custom-recovery-behavior) - - [How to write log file](#how-to-write-log-file) - - [Custom Log Format](#custom-log-format) - - [Controlling Log output coloring](#controlling-log-output-coloring) - - [Model binding and validation](#model-binding-and-validation) - - [Custom Validators](#custom-validators) - - [Only Bind Query String](#only-bind-query-string) - - [Bind Query String or Post Data](#bind-query-string-or-post-data) - - [Bind Uri](#bind-uri) - - [Bind Header](#bind-header) - - [Bind HTML checkboxes](#bind-html-checkboxes) - - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering) - - [SecureJSON](#securejson) - - [JSONP](#jsonp) - - [AsciiJSON](#asciijson) - - [PureJSON](#purejson) - - [Serving static files](#serving-static-files) - - [Serving data from file](#serving-data-from-file) - - [Serving data from reader](#serving-data-from-reader) - - [HTML rendering](#html-rendering) - - [Custom Template renderer](#custom-template-renderer) - - [Custom Delimiters](#custom-delimiters) - - [Custom Template Funcs](#custom-template-funcs) - - [Multitemplate](#multitemplate) - - [Redirects](#redirects) - - [Custom Middleware](#custom-middleware) - - [Using BasicAuth() middleware](#using-basicauth-middleware) - - [Goroutines inside a middleware](#goroutines-inside-a-middleware) - - [Custom HTTP configuration](#custom-http-configuration) - - [Support Let's Encrypt](#support-lets-encrypt) - - [Run multiple service using Gin](#run-multiple-service-using-gin) - - [Graceful shutdown or restart](#graceful-shutdown-or-restart) - - [Third-party packages](#third-party-packages) - - [Manually](#manually) - - [Build a single binary with templates](#build-a-single-binary-with-templates) - - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag) - - [http2 server push](#http2-server-push) - - [Define format for the log of routes](#define-format-for-the-log-of-routes) - - [Set and get a cookie](#set-and-get-a-cookie) - - [Don't trust all proxies](#dont-trust-all-proxies) - - [Testing](#testing) - - [Users](#users) +- Zero allocation router +- Fast +- Middleware support +- Crash-free +- JSON validation +- Routes grouping +- Error management +- Rendering built-in +- Extendable -## Installation -To install Gin package, you need to install Go and set your Go workspace first. +## Getting started -1. You first need [Go](https://go.dev/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin. +### Prerequisites -```sh -go get -u github.com/gin-gonic/gin -``` +- **[Go](https://go.dev/)**: ~~any one of the **three latest major** [releases](https://go.dev/doc/devel/release)~~ (now version **1.16+** is required). -2. Import it in your code: +### Getting Gin -```go +With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import + +``` import "github.com/gin-gonic/gin" ``` -3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. - -```go -import "net/http" -``` +to your code, and then `go [build|run|test]` will automatically fetch the necessary dependencies. -## Quick start +Otherwise, run the following Go command to install the `gin` package: ```sh -# assume the following codes in example.go file -$ cat example.go +$ go get -u github.com/gin-gonic/gin ``` +### Running Gin + +First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`: + ```go package main @@ -132,16 +73,48 @@ func main() { } ``` +And use the Go command to run the demo: + ``` -# run example.go and visit 0.0.0.0:8080/ping (for windows "localhost:8080/ping") on browser +# run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go ``` -## Benchmarks +### Learn more examples + +#### Quick Start + +Learn and practice more examples, please read the [Gin Quick Start](docs/doc.md) which includes API examples and builds tag. + +#### Examples + +A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. -Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) -[See all benchmarks](/BENCHMARKS.md) +## Documentation + +See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. + +All documentation is available on the Gin website. + +- [English](https://gin-gonic.com/docs/) +- [简体中文](https://gin-gonic.com/zh-cn/docs/) +- [繁體中文](https://gin-gonic.com/zh-tw/docs/) +- [日本語](https://gin-gonic.com/ja/docs/) +- [Español](https://gin-gonic.com/es/docs/) +- [한국어](https://gin-gonic.com/ko-kr/docs/) +- [Turkish](https://gin-gonic.com/tr/docs/) +- [Persian](https://gin-gonic.com/fa/docs/) + +### Articles about Gin + +A curated list of awesome Gin framework. + +- [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin) + +## Benchmarks + +Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks details](/BENCHMARKS.md). | Benchmark name | (1) | (2) | (3) | (4) | | ------------------------------ | ---------:| ---------------:| ------------:| ---------------:| @@ -181,2202 +154,27 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better -## Gin v1. stable - -- [x] Zero allocation router. -- [x] Still the fastest http router and framework. From routing to writing. -- [x] Complete suite of unit tests. -- [x] Battle tested. -- [x] API frozen, new releases will not break your code. - -## Build with json replacement - -Gin uses `encoding/json` as default json package but you can change it by build from other tags. - -[jsoniter](https://github.com/json-iterator/go) - -```sh -go build -tags=jsoniter . -``` - -[go-json](https://github.com/goccy/go-json) - -```sh -go build -tags=go_json . -``` - -[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.) - -```sh -$ go build -tags="sonic avx" . -``` - -## Build without `MsgPack` rendering feature - -Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag. - -```sh -go build -tags=nomsgpack . -``` - -This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852). - -## API Examples - -You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). - -### Using GET, POST, PUT, PATCH, DELETE and OPTIONS - -```go -func main() { - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/someGet", getting) - router.POST("/somePost", posting) - router.PUT("/somePut", putting) - router.DELETE("/someDelete", deleting) - router.PATCH("/somePatch", patching) - router.HEAD("/someHead", head) - router.OPTIONS("/someOptions", options) - - // By default it serves on :8080 unless a - // PORT environment variable was defined. - router.Run() - // router.Run(":3000") for a hard coded port -} -``` - -### Parameters in path - -```go -func main() { - router := gin.Default() - - // This handler will match /user/john but will not match /user/ or /user - router.GET("/user/:name", func(c *gin.Context) { - name := c.Param("name") - c.String(http.StatusOK, "Hello %s", name) - }) - - // However, this one will match /user/john/ and also /user/john/send - // If no other routers match /user/john, it will redirect to /user/john/ - router.GET("/user/:name/*action", func(c *gin.Context) { - name := c.Param("name") - action := c.Param("action") - message := name + " is " + action - c.String(http.StatusOK, message) - }) - - // For each matched request Context will hold the route definition - router.POST("/user/:name/*action", func(c *gin.Context) { - b := c.FullPath() == "/user/:name/*action" // true - c.String(http.StatusOK, "%t", b) - }) - - // This handler will add a new router for /user/groups. - // Exact routes are resolved before param routes, regardless of the order they were defined. - // Routes starting with /user/groups are never interpreted as /user/:name/... routes - router.GET("/user/groups", func(c *gin.Context) { - c.String(http.StatusOK, "The available groups are [...]") - }) - - router.Run(":8080") -} -``` - -### Querystring parameters - -```go -func main() { - router := gin.Default() - - // Query string parameters are parsed using the existing underlying request object. - // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe - router.GET("/welcome", func(c *gin.Context) { - firstname := c.DefaultQuery("firstname", "Guest") - lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") - - c.String(http.StatusOK, "Hello %s %s", firstname, lastname) - }) - router.Run(":8080") -} -``` - -### Multipart/Urlencoded Form - -```go -func main() { - router := gin.Default() - - router.POST("/form_post", func(c *gin.Context) { - message := c.PostForm("message") - nick := c.DefaultPostForm("nick", "anonymous") - - c.JSON(http.StatusOK, gin.H{ - "status": "posted", - "message": message, - "nick": nick, - }) - }) - router.Run(":8080") -} -``` - -### Another example: query + post form - -```sh -POST /post?id=1234&page=1 HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -name=manu&message=this_is_great -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - id := c.Query("id") - page := c.DefaultQuery("page", "0") - name := c.PostForm("name") - message := c.PostForm("message") - - fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) - }) - router.Run(":8080") -} -``` - -```sh -id: 1234; page: 1; name: manu; message: this_is_great -``` - -### Map as querystring or postform parameters - -```sh -POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -names[first]=thinkerou&names[second]=tianou -``` - -```go -func main() { - router := gin.Default() - - router.POST("/post", func(c *gin.Context) { - - ids := c.QueryMap("ids") - names := c.PostFormMap("names") - - fmt.Printf("ids: %v; names: %v", ids, names) - }) - router.Run(":8080") -} -``` - -```sh -ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou] -``` - -### Upload files - -#### Single file - -References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single). -`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) +## Middlewares -> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. +You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // Single file - file, _ := c.FormFile("file") - log.Println(file.Filename) - - // Upload the file to specific dst. - c.SaveUploadedFile(file, dst) - - c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) - }) - router.Run(":8080") -} -``` - -How to `curl`: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "file=@/Users/appleboy/test.zip" \ - -H "Content-Type: multipart/form-data" -``` - -#### Multiple files - -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). - -```go -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.POST("/upload", func(c *gin.Context) { - // Multipart form - form, _ := c.MultipartForm() - files := form.File["upload[]"] - - for _, file := range files { - log.Println(file.Filename) - - // Upload the file to specific dst. - c.SaveUploadedFile(file, dst) - } - c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) - }) - router.Run(":8080") -} -``` - -How to `curl`: - -```bash -curl -X POST http://localhost:8080/upload \ - -F "upload[]=@/Users/appleboy/test1.zip" \ - -F "upload[]=@/Users/appleboy/test2.zip" \ - -H "Content-Type: multipart/form-data" -``` - -### Grouping routes - -```go -func main() { - router := gin.Default() - - // Simple group: v1 - v1 := router.Group("/v1") - { - v1.POST("/login", loginEndpoint) - v1.POST("/submit", submitEndpoint) - v1.POST("/read", readEndpoint) - } - - // Simple group: v2 - v2 := router.Group("/v2") - { - v2.POST("/login", loginEndpoint) - v2.POST("/submit", submitEndpoint) - v2.POST("/read", readEndpoint) - } - - router.Run(":8080") -} -``` - -### Blank Gin without middleware by default - -Use - -```go -r := gin.New() -``` - -instead of - -```go -// Default With the Logger and Recovery middleware already attached -r := gin.Default() -``` - -### Using middleware - -```go -func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. - // By default gin.DefaultWriter = os.Stdout - r.Use(gin.Logger()) - - // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.Recovery()) - - // Per route middleware, you can add as many as you desire. - r.GET("/benchmark", MyBenchLogger(), benchEndpoint) - - // Authorization group - // authorized := r.Group("/", AuthRequired()) - // exactly the same as: - authorized := r.Group("/") - // per group middleware! in this case we use the custom created - // AuthRequired() middleware just in the "authorized" group. - authorized.Use(AuthRequired()) - { - authorized.POST("/login", loginEndpoint) - authorized.POST("/submit", submitEndpoint) - authorized.POST("/read", readEndpoint) - - // nested group - testing := authorized.Group("testing") - // visit 0.0.0.0:8080/testing/analytics - testing.GET("/analytics", analyticsEndpoint) - } - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Custom Recovery behavior - -```go -func main() { - // Creates a router without any middleware by default - r := gin.New() - - // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. - // By default gin.DefaultWriter = os.Stdout - r.Use(gin.Logger()) - - // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { - if err, ok := recovered.(string); ok { - c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) - } - c.AbortWithStatus(http.StatusInternalServerError) - })) - - r.GET("/panic", func(c *gin.Context) { - // panic with a string -- the custom middleware could save this to a database or report it to the user - panic("foo") - }) - - r.GET("/", func(c *gin.Context) { - c.String(http.StatusOK, "ohai") - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### How to write log file - -```go -func main() { - // Disable Console Color, you don't need console color when writing the logs to file. - gin.DisableConsoleColor() - - // Logging to a file. - f, _ := os.Create("gin.log") - gin.DefaultWriter = io.MultiWriter(f) - - // Use the following code if you need to write the logs to file and console at the same time. - // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) - - router := gin.Default() - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - -    router.Run(":8080") -} -``` - -### Custom Log Format - -```go -func main() { - router := gin.New() - - // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter - // By default gin.DefaultWriter = os.Stdout - router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { - - // your custom format - return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", - param.ClientIP, - param.TimeStamp.Format(time.RFC1123), - param.Method, - param.Path, - param.Request.Proto, - param.StatusCode, - param.Latency, - param.Request.UserAgent(), - param.ErrorMessage, - ) - })) - router.Use(gin.Recovery()) - - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - router.Run(":8080") -} -``` - -Sample Output - -```sh -::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " -``` - -### Controlling Log output coloring - -By default, logs output on console should be colorized depending on the detected TTY. - -Never colorize logs: - -```go -func main() { - // Disable log's color - gin.DisableConsoleColor() - - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - router.Run(":8080") -} -``` - -Always colorize logs: - -```go -func main() { - // Force log's color - gin.ForceConsoleColor() - - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() - - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - router.Run(":8080") -} -``` - -### Model binding and validation - -To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz). -Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). - -Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. +## Users -Also, Gin provides two sets of methods for binding: +Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. -- **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML` - - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. -- **Type** - Should bind - - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`, - - **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. +* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. +* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. +* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. +* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. +* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. +* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. +* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. -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 an empty value when binding, an error will be returned. +## Contributing -```go -// Binding from JSON -type Login struct { - User string `form:"user" json:"user" xml:"user" binding:"required"` - Password string `form:"password" json:"password" xml:"password" binding:"required"` -} +Gin is the work of hundreds of contributors. We appreciate your help! -func main() { - router := gin.Default() - - // Example for binding JSON ({"user": "manu", "password": "123"}) - router.POST("/loginJSON", func(c *gin.Context) { - var json Login - if err := c.ShouldBindJSON(&json); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if json.User != "manu" || json.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding XML ( - // - // - // manu - // 123 - // ) - router.POST("/loginXML", func(c *gin.Context) { - var xml Login - if err := c.ShouldBindXML(&xml); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if xml.User != "manu" || xml.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Example for binding a HTML form (user=manu&password=123) - router.POST("/loginForm", func(c *gin.Context) { - var form Login - // This will infer what binder to use depending on the content-type header. - if err := c.ShouldBind(&form); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if form.User != "manu" || form.Password != "123" { - c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) - return - } - - c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) - }) - - // Listen and serve on 0.0.0.0:8080 - router.Run(":8080") -} -``` - -Sample request - -```sh -$ curl -v -X POST \ - http://localhost:8080/loginJSON \ - -H 'content-type: application/json' \ - -d '{ "user": "manu" }' -> POST /loginJSON HTTP/1.1 -> Host: localhost:8080 -> User-Agent: curl/7.51.0 -> Accept: */* -> content-type: application/json -> Content-Length: 18 -> -* upload completely sent off: 18 out of 18 bytes -< HTTP/1.1 400 Bad Request -< Content-Type: application/json; charset=utf-8 -< Date: Fri, 04 Aug 2017 03:51:31 GMT -< Content-Length: 100 -< -{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} -``` - -Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. - -### Custom Validators - -It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). - -```go -package main - -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - "github.com/go-playground/validator/v10" -) - -// Booking contains binded and validated data. -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"` -} - -var bookableDate validator.Func = func(fl validator.FieldLevel) bool { - date, ok := fl.Field().Interface().(time.Time) - if ok { - today := time.Now() - if today.After(date) { - return false - } - } - return true -} - -func main() { - route := gin.Default() - - if v, ok := binding.Validator.Engine().(*validator.Validate); ok { - v.RegisterValidation("bookabledate", bookableDate) - } - - route.GET("/bookable", getBookable) - route.Run(":8085") -} - -func getBookable(c *gin.Context) { - var b Booking - if err := c.ShouldBindWith(&b, binding.Query); err == nil { - c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) - } else { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - } -} -``` - -```console -$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17" -{"message":"Booking dates are valid!"} - -$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09" -{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} - -$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10" -{"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 registered this way. -See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. - -### Only Bind Query String - -`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). - -```go -package main - -import ( - "log" - "net/http" - - "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(http.StatusOK, "Success") -} - -``` - -### Bind Query String or Post Data - -See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). - -```go -package main - -import ( - "log" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -type Person struct { - Name string `form:"name"` - Address string `form:"address"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - UnixTime time.Time `form:"unixTime" time_format:"unix"` -} - -func main() { - route := gin.Default() - route.GET("/testing", startPage) - route.Run(":8085") -} - -func startPage(c *gin.Context) { - var person Person - // If `GET`, only `Form` binding engine (`query`) used. - // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). - // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88 - if c.ShouldBind(&person) == nil { - log.Println(person.Name) - log.Println(person.Address) - log.Println(person.Birthday) - log.Println(person.CreateTime) - log.Println(person.UnixTime) - } - - c.String(http.StatusOK, "Success") -} -``` - -Test it with: - -```sh -curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" -``` - -### Bind Uri - -See the [detail information](https://github.com/gin-gonic/gin/issues/846). - -```go -package main - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -type Person struct { - ID string `uri:"id" binding:"required,uuid"` - Name string `uri:"name" binding:"required"` -} - -func main() { - route := gin.Default() - route.GET("/:name/:id", func(c *gin.Context) { - var person Person - if err := c.ShouldBindUri(&person); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) - return - } - c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) - }) - route.Run(":8088") -} -``` - -Test it with: - -```sh -curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 -curl -v localhost:8088/thinkerou/not-uuid -``` - -### Bind Header - -```go -package main - -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" -) - -type testHeader struct { - Rate int `header:"Rate"` - Domain string `header:"Domain"` -} - -func main() { - r := gin.Default() - r.GET("/", func(c *gin.Context) { - h := testHeader{} - - if err := c.ShouldBindHeader(&h); err != nil { - c.JSON(http.StatusOK, err) - } - - fmt.Printf("%#v\n", h) - c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain}) - }) - - r.Run() - -// client -// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ -// output -// {"Domain":"music","Rate":300} -} -``` - -### Bind HTML checkboxes - -See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) - -main.go - -```go -... - -type myForm struct { - Colors []string `form:"colors[]"` -} - -... - -func formHandler(c *gin.Context) { - var fakeForm myForm - c.ShouldBind(&fakeForm) - c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors}) -} - -... - -``` - -form.html - -```html -
-

Check some colors

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

- {{ .title }} -

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

- {{ .title }} -

-

Using posts/index.tmpl

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

- {{ .title }} -

-

Using users/index.tmpl

- -{{ end }} -``` - -#### Custom Template renderer - -You can also use your own html template render - -```go -import "html/template" - -func main() { - router := gin.Default() - html := template.Must(template.ParseFiles("file1", "file2")) - router.SetHTMLTemplate(html) - router.Run(":8080") -} -``` - -#### Custom Delimiters - -You may use custom delims - -```go - r := gin.Default() - r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates") -``` - -#### Custom Template Funcs - -See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). - -main.go - -```go -import ( - "fmt" - "html/template" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -func formatAsDate(t time.Time) string { - year, month, day := t.Date() - return fmt.Sprintf("%d/%02d/%02d", year, month, day) -} - -func main() { - router := gin.Default() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("./testdata/template/raw.tmpl") - - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", gin.H{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - - router.Run(":8080") -} - -``` - -raw.tmpl - -```html -Date: {[{.now | formatAsDate}]} -``` - -Result: - -```sh -Date: 2017/07/01 -``` - -### Multitemplate - -Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. - -### Redirects - -Issuing a HTTP redirect is easy. Both internal and external locations are supported. - -```go -r.GET("/test", func(c *gin.Context) { - c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") -}) -``` - -Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444) - -```go -r.POST("/test", func(c *gin.Context) { - c.Redirect(http.StatusFound, "/foo") -}) -``` - -Issuing a Router redirect, use `HandleContext` like below. - -``` go -r.GET("/test", func(c *gin.Context) { - c.Request.URL.Path = "/test2" - r.HandleContext(c) -}) -r.GET("/test2", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"hello": "world"}) -}) -``` - -### Custom Middleware - -```go -func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - t := time.Now() - - // Set example variable - c.Set("example", "12345") - - // before request - - c.Next() - - // after request - latency := time.Since(t) - log.Print(latency) - - // access the status we are sending - status := c.Writer.Status() - log.Println(status) - } -} - -func main() { - r := gin.New() - r.Use(Logger()) - - r.GET("/test", func(c *gin.Context) { - example := c.MustGet("example").(string) - - // it would print: "12345" - log.Println(example) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Using BasicAuth() middleware - -```go -// simulate some private data -var secrets = gin.H{ - "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, - "austin": gin.H{"email": "austin@example.com", "phone": "666"}, - "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, -} - -func main() { - r := gin.Default() - - // Group using gin.BasicAuth() middleware - // gin.Accounts is a shortcut for map[string]string - authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ - "foo": "bar", - "austin": "1234", - "lena": "hello2", - "manu": "4321", - })) - - // /admin/secrets endpoint - // hit "localhost:8080/admin/secrets - authorized.GET("/secrets", func(c *gin.Context) { - // get user, it was set by the BasicAuth middleware - user := c.MustGet(gin.AuthUserKey).(string) - if secret, ok := secrets[user]; ok { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) - } else { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) - } - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Goroutines inside a middleware - -When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. - -```go -func main() { - r := gin.Default() - - r.GET("/long_async", func(c *gin.Context) { - // create copy to be used inside the goroutine - cCp := c.Copy() - go func() { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // note that you are using the copied context "cCp", IMPORTANT - log.Println("Done! in path " + cCp.Request.URL.Path) - }() - }) - - r.GET("/long_sync", func(c *gin.Context) { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // since we are NOT using a goroutine, we do not have to copy the context - log.Println("Done! in path " + c.Request.URL.Path) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### Custom HTTP configuration - -Use `http.ListenAndServe()` directly, like this: - -```go -func main() { - router := gin.Default() - http.ListenAndServe(":8080", router) -} -``` - -or - -```go -func main() { - router := gin.Default() - - s := &http.Server{ - Addr: ":8080", - Handler: router, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - s.ListenAndServe() -} -``` - -### Support Let's Encrypt - -example for 1-line LetsEncrypt HTTPS servers. - -```go -package main - -import ( - "log" - "net/http" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - log.Fatal(autotls.Run(r, "example1.com", "example2.com")) -} -``` - -example for custom autocert manager. - -```go -package main - -import ( - "log" - "net/http" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" - "golang.org/x/crypto/acme/autocert" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), - Cache: autocert.DirCache("/var/www/.cache"), - } - - log.Fatal(autotls.RunWithManager(r, &m)) -} -``` - -### Run multiple service using Gin - -See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: - -```go -package main - -import ( - "log" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "golang.org/x/sync/errgroup" -) - -var ( - g errgroup.Group -) - -func router01() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 01", - }, - ) - }) - - return e -} - -func router02() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 02", - }, - ) - }) - - return e -} - -func main() { - server01 := &http.Server{ - Addr: ":8080", - Handler: router01(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - server02 := &http.Server{ - Addr: ":8081", - Handler: router02(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - g.Go(func() error { - err := server01.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Fatal(err) - } - return err - }) - - g.Go(func() error { - err := server02.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - log.Fatal(err) - } - return err - }) - - if err := g.Wait(); err != nil { - log.Fatal(err) - } -} -``` - -### Graceful shutdown or restart - -There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages. - -#### Third-party packages - -We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. - -```go -router := gin.Default() -router.GET("/", handler) -// [...] -endless.ListenAndServe(":4242", router) -``` - -Alternatives: - -* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. -* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. -* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. - -#### Manually - -In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). - -```go -// +build go1.8 - -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - router.GET("/", func(c *gin.Context) { - time.Sleep(5 * time.Second) - c.String(http.StatusOK, "Welcome Gin Server") - }) - - srv := &http.Server{ - Addr: ":8080", - Handler: router, - } - - // Initializing the server in a goroutine so that - // it won't block the graceful shutdown handling below - go func() { - if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Printf("listen: %s\n", err) - } - }() - - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 5 seconds. - quit := make(chan os.Signal) - // kill (no param) default send syscall.SIGTERM - // kill -2 is syscall.SIGINT - // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Println("Shutting down server...") - - // The context is used to inform the server it has 5 seconds to finish - // the request it is currently handling - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server forced to shutdown:", err) - } - - log.Println("Server exiting") -} -``` - -### Build a single binary with templates - -You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package. - -```go -package main - -import ( - "embed" - "html/template" - "net/http" - - "github.com/gin-gonic/gin" -) - -//go:embed assets/* templates/* -var f embed.FS - -func main() { - router := gin.Default() - templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) - router.SetHTMLTemplate(templ) - - // example: /public/assets/images/example.png - router.StaticFS("/public", http.FS(f)) - - router.GET("/", func(c *gin.Context) { - c.HTML(http.StatusOK, "index.tmpl", gin.H{ - "title": "Main website", - }) - }) - - router.GET("/foo", func(c *gin.Context) { - c.HTML(http.StatusOK, "bar.tmpl", gin.H{ - "title": "Foo website", - }) - }) - - router.GET("favicon.ico", func(c *gin.Context) { - file, _ := f.ReadFile("assets/favicon.ico") - c.Data( - http.StatusOK, - "image/x-icon", - file, - ) - }) - - router.Run(":8080") -} -``` - -See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory. - -### Bind form-data request with custom struct - -The follow example using custom struct: - -```go -type StructA struct { - FieldA string `form:"field_a"` -} - -type StructB struct { - NestedStruct StructA - FieldB string `form:"field_b"` -} - -type StructC struct { - NestedStructPointer *StructA - FieldC string `form:"field_c"` -} - -type StructD struct { - NestedAnonyStruct struct { - FieldX string `form:"field_x"` - } - FieldD string `form:"field_d"` -} - -func GetDataB(c *gin.Context) { - var b StructB - c.Bind(&b) - c.JSON(http.StatusOK, gin.H{ - "a": b.NestedStruct, - "b": b.FieldB, - }) -} - -func GetDataC(c *gin.Context) { - var b StructC - c.Bind(&b) - c.JSON(http.StatusOK, gin.H{ - "a": b.NestedStructPointer, - "c": b.FieldC, - }) -} - -func GetDataD(c *gin.Context) { - var b StructD - c.Bind(&b) - c.JSON(http.StatusOK, gin.H{ - "x": b.NestedAnonyStruct, - "d": b.FieldD, - }) -} - -func main() { - r := gin.Default() - r.GET("/getb", GetDataB) - r.GET("/getc", GetDataC) - r.GET("/getd", GetDataD) - - r.Run() -} -``` - -Using the command `curl` command result: - -```sh -$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" -{"a":{"FieldA":"hello"},"b":"world"} -$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" -{"a":{"FieldA":"hello"},"c":"world"} -$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" -{"d":"world","x":{"FieldX":"hello"}} -``` - -### Try to bind body into different structs - -The normal methods for binding request body consumes `c.Request.Body` and they -cannot be called multiple times. - -```go -type formA struct { - Foo string `json:"foo" xml:"foo" binding:"required"` -} - -type formB struct { - Bar string `json:"bar" xml:"bar" binding:"required"` -} - -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // 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.Form); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // At this time, it reuses body stored in the context. - } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { - c.String(http.StatusOK, `the body should be formB JSON`) - // And it can accepts other formats - } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { - c.String(http.StatusOK, `the body should be formB XML`) - } else { - ... - } -} -``` - -1. `c.ShouldBindBodyWith` stores body into the context before binding. This has -a slight impact to performance, so you should not use this method if you are -enough to call binding at once. -2. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, -`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, -can be called by `c.ShouldBind()` multiple times without any damage to -performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). - -### Bind form-data request with custom struct and custom tag - -```go -const ( - customerTag = "url" - defaultMemory = 32 << 20 -) - -type customerBinding struct {} - -func (customerBinding) Name() string { - return "form" -} - -func (customerBinding) Bind(req *http.Request, obj interface{}) error { - if err := req.ParseForm(); err != nil { - return err - } - if err := req.ParseMultipartForm(defaultMemory); err != nil { - if err != http.ErrNotMultipart { - return err - } - } - if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { - return err - } - return validate(obj) -} - -func validate(obj interface{}) error { - if binding.Validator == nil { - return nil - } - return binding.Validator.ValidateStruct(obj) -} - -// Now we can do this!!! -// FormA is an external type that we can't modify it's tag -type FormA struct { - FieldA string `url:"field_a"` -} - -func ListHandler(s *Service) func(ctx *gin.Context) { - return func(ctx *gin.Context) { - var urlBinding = customerBinding{} - var opt FormA - err := ctx.MustBindWith(&opt, urlBinding) - if err != nil { - ... - } - ... - } -} -``` - -### http2 server push - -http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information. - -```go -package main - -import ( - "html/template" - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -var html = template.Must(template.New("https").Parse(` - - - Https Test - - - -

Welcome, Ginner!

- - -`)) - -func main() { - r := gin.Default() - r.Static("/assets", "./assets") - r.SetHTMLTemplate(html) - - r.GET("/", func(c *gin.Context) { - if pusher := c.Writer.Pusher(); pusher != nil { - // use pusher.Push() to do server push - if err := pusher.Push("/assets/app.js", nil); err != nil { - log.Printf("Failed to push: %v", err) - } - } - c.HTML(http.StatusOK, "https", gin.H{ - "status": "success", - }) - }) - - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") -} -``` - -### Define format for the log of routes - -The default log of routes is: - -```sh -[GIN-debug] POST /foo --> main.main.func1 (3 handlers) -[GIN-debug] GET /bar --> main.main.func2 (3 handlers) -[GIN-debug] GET /status --> main.main.func3 (3 handlers) -``` - -If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. -In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. - -```go -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { - log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) - } - - r.POST("/foo", func(c *gin.Context) { - c.JSON(http.StatusOK, "foo") - }) - - r.GET("/bar", func(c *gin.Context) { - c.JSON(http.StatusOK, "bar") - }) - - r.GET("/status", func(c *gin.Context) { - c.JSON(http.StatusOK, "ok") - }) - - // Listen and Server in http://0.0.0.0:8080 - r.Run() -} -``` - -### Set and get a cookie - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - - router.GET("/cookie", func(c *gin.Context) { - - cookie, err := c.Cookie("gin_cookie") - - if err != nil { - cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) - } - - fmt.Printf("Cookie value: %s \n", cookie) - }) - - router.Run() -} -``` - -## Don't trust all proxies - -Gin lets you specify which headers to hold the real client IP (if any), -as well as specifying which proxies (or direct clients) you trust to -specify one of these headers. - -Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses -or network CIDRs from where clients which their request headers related to client -IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or -IPv6 CIDRs. - -**Attention:** Gin trust all proxies by default if you don't specify a trusted -proxy using the function above, **this is NOT safe**. At the same time, if you don't -use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`, -then `Context.ClientIP()` will return the remote address directly to avoid some -unnecessary computation. - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - router.SetTrustedProxies([]string{"192.168.1.2"}) - - router.GET("/", func(c *gin.Context) { - // If the client is 192.168.1.2, use the X-Forwarded-For - // header to deduce the original client IP from the trust- - // worthy parts of that header. - // Otherwise, simply return the direct client IP - fmt.Printf("ClientIP: %s\n", c.ClientIP()) - }) - router.Run() -} -``` - -**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` -to skip TrustedProxies check, it has a higher priority than TrustedProxies. -Look at the example below: - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - // Use predefined header gin.PlatformXXX - router.TrustedPlatform = gin.PlatformGoogleAppEngine - // Or set your own trusted request header for another trusted proxy service - // Don't set it to any suspect request header, it's unsafe - router.TrustedPlatform = "X-CDN-IP" - - router.GET("/", func(c *gin.Context) { - // If you set TrustedPlatform, ClientIP() will resolve the - // corresponding header and return IP directly - fmt.Printf("ClientIP: %s\n", c.ClientIP()) - }) - router.Run() -} -``` - -## Testing - -The `net/http/httptest` package is preferable way for HTTP testing. - -```go -package main - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -func setupRouter() *gin.Engine { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "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(http.MethodGet, "/ping", nil) - router.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "pong", w.Body.String()) -} -``` - -## Users - -Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. - -* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. -* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. -* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. -* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. -* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. -* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. +Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. \ No newline at end of file diff --git a/docs/doc.md b/docs/doc.md new file mode 100644 index 0000000..008a91d --- /dev/null +++ b/docs/doc.md @@ -0,0 +1,2246 @@ +# Gin Quick Start + +## Contents + +- [Build Tags](#build-tags) + - [Build with json replacement](#build-with-json-replacement) + - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature) +- [API Examples](#api-examples) + - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) + - [Parameters in path](#parameters-in-path) + - [Querystring parameters](#querystring-parameters) + - [Multipart/Urlencoded Form](#multiparturlencoded-form) + - [Another example: query + post form](#another-example-query--post-form) + - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) + - [Upload files](#upload-files) + - [Single file](#single-file) + - [Multiple files](#multiple-files) + - [Grouping routes](#grouping-routes) + - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) + - [Using middleware](#using-middleware) + - [Custom Recovery behavior](#custom-recovery-behavior) + - [How to write log file](#how-to-write-log-file) + - [Custom Log Format](#custom-log-format) + - [Controlling Log output coloring](#controlling-log-output-coloring) + - [Model binding and validation](#model-binding-and-validation) + - [Custom Validators](#custom-validators) + - [Only Bind Query String](#only-bind-query-string) + - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Bind Uri](#bind-uri) + - [Bind Header](#bind-header) + - [Bind HTML checkboxes](#bind-html-checkboxes) + - [Multipart/Urlencoded binding](#multiparturlencoded-binding) + - [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering) + - [SecureJSON](#securejson) + - [JSONP](#jsonp) + - [AsciiJSON](#asciijson) + - [PureJSON](#purejson) + - [Serving static files](#serving-static-files) + - [Serving data from file](#serving-data-from-file) + - [Serving data from reader](#serving-data-from-reader) + - [HTML rendering](#html-rendering) + - [Custom Template renderer](#custom-template-renderer) + - [Custom Delimiters](#custom-delimiters) + - [Custom Template Funcs](#custom-template-funcs) + - [Multitemplate](#multitemplate) + - [Redirects](#redirects) + - [Custom Middleware](#custom-middleware) + - [Using BasicAuth() middleware](#using-basicauth-middleware) + - [Goroutines inside a middleware](#goroutines-inside-a-middleware) + - [Custom HTTP configuration](#custom-http-configuration) + - [Support Let's Encrypt](#support-lets-encrypt) + - [Run multiple service using Gin](#run-multiple-service-using-gin) + - [Graceful shutdown or restart](#graceful-shutdown-or-restart) + - [Third-party packages](#third-party-packages) + - [Manually](#manually) + - [Build a single binary with templates](#build-a-single-binary-with-templates) + - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) + - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) + - [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag) + - [http2 server push](#http2-server-push) + - [Define format for the log of routes](#define-format-for-the-log-of-routes) + - [Set and get a cookie](#set-and-get-a-cookie) +- [Don't trust all proxies](#dont-trust-all-proxies) +- [Testing](#testing) + +## Build tags + +### Build with json replacement + +Gin uses `encoding/json` as default json package but you can change it by build from other tags. + +[jsoniter](https://github.com/json-iterator/go) + +```sh +go build -tags=jsoniter . +``` + +[go-json](https://github.com/goccy/go-json) + +```sh +go build -tags=go_json . +``` + +[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.) + +```sh +$ go build -tags="sonic avx" . +``` + +### Build without `MsgPack` rendering feature + +Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag. + +```sh +go build -tags=nomsgpack . +``` + +This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852). + +## API Examples + +You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). + +### Using GET, POST, PUT, PATCH, DELETE and OPTIONS + +```go +func main() { + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/someGet", getting) + router.POST("/somePost", posting) + router.PUT("/somePut", putting) + router.DELETE("/someDelete", deleting) + router.PATCH("/somePatch", patching) + router.HEAD("/someHead", head) + router.OPTIONS("/someOptions", options) + + // By default it serves on :8080 unless a + // PORT environment variable was defined. + router.Run() + // router.Run(":3000") for a hard coded port +} +``` + +### Parameters in path + +```go +func main() { + router := gin.Default() + + // This handler will match /user/john but will not match /user/ or /user + router.GET("/user/:name", func(c *gin.Context) { + name := c.Param("name") + c.String(http.StatusOK, "Hello %s", name) + }) + + // However, this one will match /user/john/ and also /user/john/send + // If no other routers match /user/john, it will redirect to /user/john/ + router.GET("/user/:name/*action", func(c *gin.Context) { + name := c.Param("name") + action := c.Param("action") + message := name + " is " + action + c.String(http.StatusOK, message) + }) + + // For each matched request Context will hold the route definition + router.POST("/user/:name/*action", func(c *gin.Context) { + b := c.FullPath() == "/user/:name/*action" // true + c.String(http.StatusOK, "%t", b) + }) + + // This handler will add a new router for /user/groups. + // Exact routes are resolved before param routes, regardless of the order they were defined. + // Routes starting with /user/groups are never interpreted as /user/:name/... routes + router.GET("/user/groups", func(c *gin.Context) { + c.String(http.StatusOK, "The available groups are [...]") + }) + + router.Run(":8080") +} +``` + +### Querystring parameters + +```go +func main() { + router := gin.Default() + + // Query string parameters are parsed using the existing underlying request object. + // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe + router.GET("/welcome", func(c *gin.Context) { + firstname := c.DefaultQuery("firstname", "Guest") + lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") + + c.String(http.StatusOK, "Hello %s %s", firstname, lastname) + }) + router.Run(":8080") +} +``` + +### Multipart/Urlencoded Form + +```go +func main() { + router := gin.Default() + + router.POST("/form_post", func(c *gin.Context) { + message := c.PostForm("message") + nick := c.DefaultPostForm("nick", "anonymous") + + c.JSON(http.StatusOK, gin.H{ + "status": "posted", + "message": message, + "nick": nick, + }) + }) + router.Run(":8080") +} +``` + +### Another example: query + post form + +```sh +POST /post?id=1234&page=1 HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +name=manu&message=this_is_great +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + id := c.Query("id") + page := c.DefaultQuery("page", "0") + name := c.PostForm("name") + message := c.PostForm("message") + + fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) + }) + router.Run(":8080") +} +``` + +```sh +id: 1234; page: 1; name: manu; message: this_is_great +``` + +### Map as querystring or postform parameters + +```sh +POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +names[first]=thinkerou&names[second]=tianou +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + ids := c.QueryMap("ids") + names := c.PostFormMap("names") + + fmt.Printf("ids: %v; names: %v", ids, names) + }) + router.Run(":8080") +} +``` + +```sh +ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou] +``` + +### Upload files + +#### Single file + +References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single). + +`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) + +> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // Single file + file, _ := c.FormFile("file") + log.Println(file.Filename) + + // Upload the file to specific dst. + c.SaveUploadedFile(file, dst) + + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "file=@/Users/appleboy/test.zip" \ + -H "Content-Type: multipart/form-data" +``` + +#### Multiple files + +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // Multipart form + form, _ := c.MultipartForm() + files := form.File["upload[]"] + + for _, file := range files { + log.Println(file.Filename) + + // Upload the file to specific dst. + c.SaveUploadedFile(file, dst) + } + c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "upload[]=@/Users/appleboy/test1.zip" \ + -F "upload[]=@/Users/appleboy/test2.zip" \ + -H "Content-Type: multipart/form-data" +``` + +### Grouping routes + +```go +func main() { + router := gin.Default() + + // Simple group: v1 + v1 := router.Group("/v1") + { + v1.POST("/login", loginEndpoint) + v1.POST("/submit", submitEndpoint) + v1.POST("/read", readEndpoint) + } + + // Simple group: v2 + v2 := router.Group("/v2") + { + v2.POST("/login", loginEndpoint) + v2.POST("/submit", submitEndpoint) + v2.POST("/read", readEndpoint) + } + + router.Run(":8080") +} +``` + +### Blank Gin without middleware by default + +Use + +```go +r := gin.New() +``` + +instead of + +```go +// Default With the Logger and Recovery middleware already attached +r := gin.Default() +``` + +### Using middleware + +```go +func main() { + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.Recovery()) + + // Per route middleware, you can add as many as you desire. + r.GET("/benchmark", MyBenchLogger(), benchEndpoint) + + // Authorization group + // authorized := r.Group("/", AuthRequired()) + // exactly the same as: + authorized := r.Group("/") + // per group middleware! in this case we use the custom created + // AuthRequired() middleware just in the "authorized" group. + authorized.Use(AuthRequired()) + { + authorized.POST("/login", loginEndpoint) + authorized.POST("/submit", submitEndpoint) + authorized.POST("/read", readEndpoint) + + // nested group + testing := authorized.Group("testing") + // visit 0.0.0.0:8080/testing/analytics + testing.GET("/analytics", analyticsEndpoint) + } + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Custom Recovery behavior + +```go +func main() { + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { + if err, ok := recovered.(string); ok { + c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) + } + c.AbortWithStatus(http.StatusInternalServerError) + })) + + r.GET("/panic", func(c *gin.Context) { + // panic with a string -- the custom middleware could save this to a database or report it to the user + panic("foo") + }) + + r.GET("/", func(c *gin.Context) { + c.String(http.StatusOK, "ohai") + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### How to write log file + +```go +func main() { + // Disable Console Color, you don't need console color when writing the logs to file. + gin.DisableConsoleColor() + + // Logging to a file. + f, _ := os.Create("gin.log") + gin.DefaultWriter = io.MultiWriter(f) + + // Use the following code if you need to write the logs to file and console at the same time. + // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) + + router := gin.Default() + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + +    router.Run(":8080") +} +``` + +### Custom Log Format + +```go +func main() { + router := gin.New() + + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + + // your custom format + return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", + param.ClientIP, + param.TimeStamp.Format(time.RFC1123), + param.Method, + param.Path, + param.Request.Proto, + param.StatusCode, + param.Latency, + param.Request.UserAgent(), + param.ErrorMessage, + ) + })) + router.Use(gin.Recovery()) + + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + router.Run(":8080") +} +``` + +Sample Output + +```sh +::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " +``` + +### Controlling Log output coloring + +By default, logs output on console should be colorized depending on the detected TTY. + +Never colorize logs: + +```go +func main() { + // Disable log's color + gin.DisableConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + router.Run(":8080") +} +``` + +Always colorize logs: + +```go +func main() { + // Force log's color + gin.ForceConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + router.Run(":8080") +} +``` + +### Model binding and validation + +To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz). + +Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags). + +Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. + +Also, Gin provides two sets of methods for binding: + +- **Type** - Must bind + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML` + - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. +- **Type** - Should bind + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`, + - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. + +When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. + +You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has an empty value when binding, an error will be returned. + +```go +// Binding from JSON +type Login struct { + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` +} + +func main() { + router := gin.Default() + + // Example for binding JSON ({"user": "manu", "password": "123"}) + router.POST("/loginJSON", func(c *gin.Context) { + var json Login + if err := c.ShouldBindJSON(&json); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding XML ( + // + // + // manu + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding a HTML form (user=manu&password=123) + router.POST("/loginForm", func(c *gin.Context) { + var form Login + // This will infer what binder to use depending on the content-type header. + if err := c.ShouldBind(&form); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Listen and serve on 0.0.0.0:8080 + router.Run(":8080") +} +``` + +Sample request + +```sh +$ curl -v -X POST \ + http://localhost:8080/loginJSON \ + -H 'content-type: application/json' \ + -d '{ "user": "manu" }' +> POST /loginJSON HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.51.0 +> Accept: */* +> content-type: application/json +> Content-Length: 18 +> +* upload completely sent off: 18 out of 18 bytes +< HTTP/1.1 400 Bad Request +< Content-Type: application/json; charset=utf-8 +< Date: Fri, 04 Aug 2017 03:51:31 GMT +< Content-Length: 100 +< +{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} +``` + +Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. + +### Custom Validators + +It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). + +```go +package main + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" +) + +// Booking contains binded and validated data. +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"` +} + +var bookableDate validator.Func = func(fl validator.FieldLevel) bool { + date, ok := fl.Field().Interface().(time.Time) + if ok { + today := time.Now() + if today.After(date) { + return false + } + } + return true +} + +func main() { + route := gin.Default() + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + + route.GET("/bookable", getBookable) + route.Run(":8085") +} + +func getBookable(c *gin.Context) { + var b Booking + if err := c.ShouldBindWith(&b, binding.Query); err == nil { + c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } +} +``` + +```console +$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17" +{"message":"Booking dates are valid!"} + +$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09" +{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"} + +$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10" +{"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 registered this way. +See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. + +### Only Bind Query String + +`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). + +```go +package main + +import ( + "log" + "net/http" + + "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(http.StatusOK, "Success") +} + +``` + +### Bind Query String or Post Data + +See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). + +```go +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` +} + +func main() { + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + // If `GET`, only `Form` binding engine (`query`) used. + // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L88 + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + log.Println(person.Birthday) + log.Println(person.CreateTime) + log.Println(person.UnixTime) + } + + c.String(http.StatusOK, "Success") +} +``` + +Test it with: + +```sh +curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" +``` + +### Bind Uri + +See the [detail information](https://github.com/gin-gonic/gin/issues/846). + +```go +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Person struct { + ID string `uri:"id" binding:"required,uuid"` + Name string `uri:"name" binding:"required"` +} + +func main() { + route := gin.Default() + route.GET("/:name/:id", func(c *gin.Context) { + var person Person + if err := c.ShouldBindUri(&person); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID}) + }) + route.Run(":8088") +} +``` + +Test it with: + +```sh +curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 +curl -v localhost:8088/thinkerou/not-uuid +``` + +### Bind Header + +```go +package main + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" +) + +type testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` +} + +func main() { + r := gin.Default() + r.GET("/", func(c *gin.Context) { + h := testHeader{} + + if err := c.ShouldBindHeader(&h); err != nil { + c.JSON(http.StatusOK, err) + } + + fmt.Printf("%#v\n", h) + c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain}) + }) + + r.Run() + +// client +// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ +// output +// {"Domain":"music","Rate":300} +} +``` + +### Bind HTML checkboxes + +See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) + +main.go + +```go +... + +type myForm struct { + Colors []string `form:"colors[]"` +} + +... + +func formHandler(c *gin.Context) { + var fakeForm myForm + c.ShouldBind(&fakeForm) + c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors}) +} + +... + +``` + +form.html + +```html +
+

Check some colors

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

+ {{ .title }} +

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

+ {{ .title }} +

+

Using posts/index.tmpl

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

+ {{ .title }} +

+

Using users/index.tmpl

+ +{{ end }} +``` + +#### Custom Template renderer + +You can also use your own html template render + +```go +import "html/template" + +func main() { + router := gin.Default() + html := template.Must(template.ParseFiles("file1", "file2")) + router.SetHTMLTemplate(html) + router.Run(":8080") +} +``` + +#### Custom Delimiters + +You may use custom delims + +```go + r := gin.Default() + r.Delims("{[{", "}]}") + r.LoadHTMLGlob("/path/to/templates") +``` + +#### Custom Template Funcs + +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template). + +main.go + +```go +import ( + "fmt" + "html/template" + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +func formatAsDate(t time.Time) string { + year, month, day := t.Date() + return fmt.Sprintf("%d/%02d/%02d", year, month, day) +} + +func main() { + router := gin.Default() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + router.LoadHTMLFiles("./testdata/template/raw.tmpl") + + router.GET("/raw", func(c *gin.Context) { + c.HTML(http.StatusOK, "raw.tmpl", gin.H{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) + + router.Run(":8080") +} + +``` + +raw.tmpl + +```html +Date: {[{.now | formatAsDate}]} +``` + +Result: + +```sh +Date: 2017/07/01 +``` + +### Multitemplate + +Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`. + +### Redirects + +Issuing a HTTP redirect is easy. Both internal and external locations are supported. + +```go +r.GET("/test", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") +}) +``` + +Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444) + +```go +r.POST("/test", func(c *gin.Context) { + c.Redirect(http.StatusFound, "/foo") +}) +``` + +Issuing a Router redirect, use `HandleContext` like below. + +``` go +r.GET("/test", func(c *gin.Context) { + c.Request.URL.Path = "/test2" + r.HandleContext(c) +}) +r.GET("/test2", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"hello": "world"}) +}) +``` + +### Custom Middleware + +```go +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + t := time.Now() + + // Set example variable + c.Set("example", "12345") + + // before request + + c.Next() + + // after request + latency := time.Since(t) + log.Print(latency) + + // access the status we are sending + status := c.Writer.Status() + log.Println(status) + } +} + +func main() { + r := gin.New() + r.Use(Logger()) + + r.GET("/test", func(c *gin.Context) { + example := c.MustGet("example").(string) + + // it would print: "12345" + log.Println(example) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Using BasicAuth() middleware + +```go +// simulate some private data +var secrets = gin.H{ + "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, + "austin": gin.H{"email": "austin@example.com", "phone": "666"}, + "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, +} + +func main() { + r := gin.Default() + + // Group using gin.BasicAuth() middleware + // gin.Accounts is a shortcut for map[string]string + authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ + "foo": "bar", + "austin": "1234", + "lena": "hello2", + "manu": "4321", + })) + + // /admin/secrets endpoint + // hit "localhost:8080/admin/secrets + authorized.GET("/secrets", func(c *gin.Context) { + // get user, it was set by the BasicAuth middleware + user := c.MustGet(gin.AuthUserKey).(string) + if secret, ok := secrets[user]; ok { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) + } else { + c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) + } + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Goroutines inside a middleware + +When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy. + +```go +func main() { + r := gin.Default() + + r.GET("/long_async", func(c *gin.Context) { + // create copy to be used inside the goroutine + cCp := c.Copy() + go func() { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // note that you are using the copied context "cCp", IMPORTANT + log.Println("Done! in path " + cCp.Request.URL.Path) + }() + }) + + r.GET("/long_sync", func(c *gin.Context) { + // simulate a long task with time.Sleep(). 5 seconds + time.Sleep(5 * time.Second) + + // since we are NOT using a goroutine, we do not have to copy the context + log.Println("Done! in path " + c.Request.URL.Path) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### Custom HTTP configuration + +Use `http.ListenAndServe()` directly, like this: + +```go +func main() { + router := gin.Default() + http.ListenAndServe(":8080", router) +} +``` + +or + +```go +func main() { + router := gin.Default() + + s := &http.Server{ + Addr: ":8080", + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + s.ListenAndServe() +} +``` + +### Support Let's Encrypt + +example for 1-line LetsEncrypt HTTPS servers. + +```go +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + log.Fatal(autotls.Run(r, "example1.com", "example2.com")) +} +``` + +example for custom autocert manager. + +```go +package main + +import ( + "log" + "net/http" + + "github.com/gin-gonic/autotls" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/acme/autocert" +) + +func main() { + r := gin.Default() + + // Ping handler + r.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + m := autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), + Cache: autocert.DirCache("/var/www/.cache"), + } + + log.Fatal(autotls.RunWithManager(r, &m)) +} +``` + +### Run multiple service using Gin + +See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: + +```go +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" +) + +var ( + g errgroup.Group +) + +func router01() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 01", + }, + ) + }) + + return e +} + +func router02() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 02", + }, + ) + }) + + return e +} + +func main() { + server01 := &http.Server{ + Addr: ":8080", + Handler: router01(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + server02 := &http.Server{ + Addr: ":8081", + Handler: router02(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + g.Go(func() error { + err := server01.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + return err + }) + + g.Go(func() error { + err := server02.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + return err + }) + + if err := g.Wait(); err != nil { + log.Fatal(err) + } +} +``` + +### Graceful shutdown or restart + +There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages. + +#### Third-party packages + +We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details. + +```go +router := gin.Default() +router.GET("/", handler) +// [...] +endless.ListenAndServe(":4242", router) +``` + +Alternatives: + +* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers. +* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server. +* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully. + +#### Manually + +In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://pkg.go.dev/net/http#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown). + +```go +// +build go1.8 + +package main + +import ( + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gin-gonic/gin" +) + +func main() { + router := gin.Default() + router.GET("/", func(c *gin.Context) { + time.Sleep(5 * time.Second) + c.String(http.StatusOK, "Welcome Gin Server") + }) + + srv := &http.Server{ + Addr: ":8080", + Handler: router, + } + + // Initializing the server in a goroutine so that + // it won't block the graceful shutdown handling below + go func() { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Printf("listen: %s\n", err) + } + }() + + // Wait for interrupt signal to gracefully shutdown the server with + // a timeout of 5 seconds. + quit := make(chan os.Signal) + // kill (no param) default send syscall.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + log.Println("Shutting down server...") + + // The context is used to inform the server it has 5 seconds to finish + // the request it is currently handling + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := srv.Shutdown(ctx); err != nil { + log.Fatal("Server forced to shutdown:", err) + } + + log.Println("Server exiting") +} +``` + +### Build a single binary with templates + +You can build a server into a single binary containing templates by using the [embed](https://pkg.go.dev/embed) package. + +```go +package main + +import ( + "embed" + "html/template" + "net/http" + + "github.com/gin-gonic/gin" +) + +//go:embed assets/* templates/* +var f embed.FS + +func main() { + router := gin.Default() + templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl")) + router.SetHTMLTemplate(templ) + + // example: /public/assets/images/example.png + router.StaticFS("/public", http.FS(f)) + + router.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.tmpl", gin.H{ + "title": "Main website", + }) + }) + + router.GET("/foo", func(c *gin.Context) { + c.HTML(http.StatusOK, "bar.tmpl", gin.H{ + "title": "Foo website", + }) + }) + + router.GET("favicon.ico", func(c *gin.Context) { + file, _ := f.ReadFile("assets/favicon.ico") + c.Data( + http.StatusOK, + "image/x-icon", + file, + ) + }) + + router.Run(":8080") +} +``` + +See a complete example in the `https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example02` directory. + +### Bind form-data request with custom struct + +The follow example using custom struct: + +```go +type StructA struct { + FieldA string `form:"field_a"` +} + +type StructB struct { + NestedStruct StructA + FieldB string `form:"field_b"` +} + +type StructC struct { + NestedStructPointer *StructA + FieldC string `form:"field_c"` +} + +type StructD struct { + NestedAnonyStruct struct { + FieldX string `form:"field_x"` + } + FieldD string `form:"field_d"` +} + +func GetDataB(c *gin.Context) { + var b StructB + c.Bind(&b) + c.JSON(http.StatusOK, gin.H{ + "a": b.NestedStruct, + "b": b.FieldB, + }) +} + +func GetDataC(c *gin.Context) { + var b StructC + c.Bind(&b) + c.JSON(http.StatusOK, gin.H{ + "a": b.NestedStructPointer, + "c": b.FieldC, + }) +} + +func GetDataD(c *gin.Context) { + var b StructD + c.Bind(&b) + c.JSON(http.StatusOK, gin.H{ + "x": b.NestedAnonyStruct, + "d": b.FieldD, + }) +} + +func main() { + r := gin.Default() + r.GET("/getb", GetDataB) + r.GET("/getc", GetDataC) + r.GET("/getd", GetDataD) + + r.Run() +} +``` + +Using the command `curl` command result: + +```sh +$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" +{"a":{"FieldA":"hello"},"b":"world"} +$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" +{"a":{"FieldA":"hello"},"c":"world"} +$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" +{"d":"world","x":{"FieldX":"hello"}} +``` + +### Try to bind body into different structs + +The normal methods for binding request body consumes `c.Request.Body` and they +cannot be called multiple times. + +```go +type formA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` +} + +type formB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` +} + +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // 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.Form); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // At this time, it reuses body stored in the context. + } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { + c.String(http.StatusOK, `the body should be formB JSON`) + // And it can accepts other formats + } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { + c.String(http.StatusOK, `the body should be formB XML`) + } else { + ... + } +} +``` + +1. `c.ShouldBindBodyWith` stores body into the context before binding. This has +a slight impact to performance, so you should not use this method if you are +enough to call binding at once. +2. This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, +`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, +can be called by `c.ShouldBind()` multiple times without any damage to +performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). + +### Bind form-data request with custom struct and custom tag + +```go +const ( + customerTag = "url" + defaultMemory = 32 << 20 +) + +type customerBinding struct {} + +func (customerBinding) Name() string { + return "form" +} + +func (customerBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseForm(); err != nil { + return err + } + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } + if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil { + return err + } + return validate(obj) +} + +func validate(obj interface{}) error { + if binding.Validator == nil { + return nil + } + return binding.Validator.ValidateStruct(obj) +} + +// Now we can do this!!! +// FormA is an external type that we can't modify it's tag +type FormA struct { + FieldA string `url:"field_a"` +} + +func ListHandler(s *Service) func(ctx *gin.Context) { + return func(ctx *gin.Context) { + var urlBinding = customerBinding{} + var opt FormA + err := ctx.MustBindWith(&opt, urlBinding) + if err != nil { + ... + } + ... + } +} +``` + +### http2 server push + +http.Pusher is supported only **go1.8+**. See the [golang blog](https://go.dev/blog/h2push) for detail information. + +```go +package main + +import ( + "html/template" + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

+ + +`)) + +func main() { + r := gin.Default() + r.Static("/assets", "./assets") + r.SetHTMLTemplate(html) + + r.GET("/", func(c *gin.Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + // use pusher.Push() to do server push + if err := pusher.Push("/assets/app.js", nil); err != nil { + log.Printf("Failed to push: %v", err) + } + } + c.HTML(http.StatusOK, "https", gin.H{ + "status": "success", + }) + }) + + // Listen and Server in https://127.0.0.1:8080 + r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") +} +``` + +### Define format for the log of routes + +The default log of routes is: + +```sh +[GIN-debug] POST /foo --> main.main.func1 (3 handlers) +[GIN-debug] GET /bar --> main.main.func2 (3 handlers) +[GIN-debug] GET /status --> main.main.func3 (3 handlers) +``` + +If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. +In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. + +```go +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { + log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) + } + + r.POST("/foo", func(c *gin.Context) { + c.JSON(http.StatusOK, "foo") + }) + + r.GET("/bar", func(c *gin.Context) { + c.JSON(http.StatusOK, "bar") + }) + + r.GET("/status", func(c *gin.Context) { + c.JSON(http.StatusOK, "ok") + }) + + // Listen and Server in http://0.0.0.0:8080 + r.Run() +} +``` + +### Set and get a cookie + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + + router.GET("/cookie", func(c *gin.Context) { + + cookie, err := c.Cookie("gin_cookie") + + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } + + fmt.Printf("Cookie value: %s \n", cookie) + }) + + router.Run() +} +``` + +## Don't trust all proxies + +Gin lets you specify which headers to hold the real client IP (if any), +as well as specifying which proxies (or direct clients) you trust to +specify one of these headers. + +Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses +or network CIDRs from where clients which their request headers related to client +IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or +IPv6 CIDRs. + +**Attention:** Gin trust all proxies by default if you don't specify a trusted +proxy using the function above, **this is NOT safe**. At the same time, if you don't +use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`, +then `Context.ClientIP()` will return the remote address directly to avoid some +unnecessary computation. + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + router.SetTrustedProxies([]string{"192.168.1.2"}) + + router.GET("/", func(c *gin.Context) { + // If the client is 192.168.1.2, use the X-Forwarded-For + // header to deduce the original client IP from the trust- + // worthy parts of that header. + // Otherwise, simply return the direct client IP + fmt.Printf("ClientIP: %s\n", c.ClientIP()) + }) + router.Run() +} +``` + +**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` +to skip TrustedProxies check, it has a higher priority than TrustedProxies. +Look at the example below: + +```go +import ( + "fmt" + + "github.com/gin-gonic/gin" +) + +func main() { + + router := gin.Default() + // Use predefined header gin.PlatformXXX + router.TrustedPlatform = gin.PlatformGoogleAppEngine + // Or set your own trusted request header for another trusted proxy service + // Don't set it to any suspect request header, it's unsafe + router.TrustedPlatform = "X-CDN-IP" + + router.GET("/", func(c *gin.Context) { + // If you set TrustedPlatform, ClientIP() will resolve the + // corresponding header and return IP directly + fmt.Printf("ClientIP: %s\n", c.ClientIP()) + }) + router.Run() +} +``` + +## Testing + +The `net/http/httptest` package is preferable way for HTTP testing. + +```go +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func setupRouter() *gin.Engine { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "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(http.MethodGet, "/ping", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "pong", w.Body.String()) +} +``` From 41f2669ebcc24dbbef7dcca705a4cd75b7c43f28 Mon Sep 17 00:00:00 2001 From: "Alireza (Pure)" Date: Mon, 2 Jan 2023 07:08:53 +0330 Subject: [PATCH 747/912] console logger HTTP status bug fixed and the corresponding unit test added (#3453) --- response_writer.go | 1 + response_writer_test.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/response_writer.go b/response_writer.go index 43e828d..5cec1f6 100644 --- a/response_writer.go +++ b/response_writer.go @@ -61,6 +61,7 @@ func (w *responseWriter) WriteHeader(code int) { if code > 0 && w.status != code { if w.Written() { debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code) + return } w.status = code } diff --git a/response_writer_test.go b/response_writer_test.go index 57d163c..6fa5ec7 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -132,3 +132,21 @@ func TestResponseWriterFlush(t *testing.T) { assert.NoError(t, err) assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) } + +func TestResponseWriterStatusCode(t *testing.T) { + testWriter := httptest.NewRecorder() + writer := &responseWriter{} + writer.reset(testWriter) + w := ResponseWriter(writer) + + w.WriteHeader(http.StatusOK) + w.WriteHeaderNow() + + assert.Equal(t, http.StatusOK, w.Status()) + assert.True(t, w.Written()) + + w.WriteHeader(http.StatusUnauthorized) + + // status must be 200 although we tried to change it + assert.Equal(t, http.StatusOK, w.Status()) +} From 7d8fc1563b4e1b4229e61c2fe4c9e31ce13ace7d Mon Sep 17 00:00:00 2001 From: youngxhui Date: Mon, 2 Jan 2023 11:39:26 +0800 Subject: [PATCH 748/912] update context.go Get/Set method use defer (#3429) Using defer to unlock is more in line with go standards --- context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 737e4d7..b1352b9 100644 --- a/context.go +++ b/context.go @@ -248,20 +248,20 @@ func (c *Context) Error(err error) *Error { // It also lazy initializes c.Keys if it was not used previously. func (c *Context) Set(key string, value any) { c.mu.Lock() + defer c.mu.Unlock() if c.Keys == nil { c.Keys = make(map[string]any) } c.Keys[key] = value - c.mu.Unlock() } // Get returns the value for the given key, ie: (value, true). // If the value does not exist it returns (nil, false) func (c *Context) Get(key string) (value any, exists bool) { c.mu.RLock() + defer c.mu.RUnlock() value, exists = c.Keys[key] - c.mu.RUnlock() return } From c9b27249fbb6092bcc7f749811d73ef1d50eee73 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Mon, 2 Jan 2023 12:40:48 +0800 Subject: [PATCH 749/912] chore(yaml): upgrade dependency to v3 version (#3456) fixes https://github.com/gin-gonic/gin/issues/3451 fixes https://github.com/gin-gonic/gin/issues/3306 fixes https://github.com/gin-gonic/gin/issues/3362 fixes https://github.com/gin-gonic/gin/issues/2581 --- binding/yaml.go | 2 +- go.mod | 3 +-- go.sum | 2 -- render/render_test.go | 2 +- render/yaml.go | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/binding/yaml.go b/binding/yaml.go index b0d36a3..2535f8c 100644 --- a/binding/yaml.go +++ b/binding/yaml.go @@ -9,7 +9,7 @@ import ( "io" "net/http" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) type yamlBinding struct{} diff --git a/go.mod b/go.mod index e969833..2b7a98c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/ugorji/go/codec v1.2.7 golang.org/x/net v0.4.0 google.golang.org/protobuf v1.28.1 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -32,5 +32,4 @@ require ( golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6d8df64..a4f0f38 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/render/render_test.go b/render/render_test.go index c5c5375..ebb7d41 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -238,7 +238,7 @@ b: err := (YAML{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String()) + assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String()) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/render/yaml.go b/render/yaml.go index 4f0ac01..fc927c1 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -7,7 +7,7 @@ package render import ( "net/http" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) // YAML contains the given interface object. From 7626361587bdce4b02335edd1d38627b79027b5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 10:40:53 +0800 Subject: [PATCH 750/912] chore(deps): bump github.com/ugorji/go/codec from 1.2.7 to 1.2.8 (#3458) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.2.7 to 1.2.8. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/v1.2.7...v1.2.8) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 2b7a98c..daff61d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/mattn/go-isatty v0.0.16 github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 - github.com/ugorji/go/codec v1.2.7 + github.com/ugorji/go/codec v1.2.8 golang.org/x/net v0.4.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index a4f0f38..c7e0b38 100644 --- a/go.sum +++ b/go.sum @@ -65,9 +65,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0= +github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= From 79a61b90324586d3c3a5859f8755cae2d1c46f2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 11:39:57 +0800 Subject: [PATCH 751/912] chore(deps): bump github.com/mattn/go-isatty from 0.0.16 to 0.0.17 (#3457) Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.16 to 0.0.17. - [Release notes](https://github.com/mattn/go-isatty/releases) - [Commits](https://github.com/mattn/go-isatty/compare/v0.0.16...v0.0.17) --- updated-dependencies: - dependency-name: github.com/mattn/go-isatty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index daff61d..8cb21f9 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-playground/validator/v10 v10.11.1 github.com/goccy/go-json v0.10.0 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-isatty v0.0.17 github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.8 diff --git a/go.sum b/go.sum index c7e0b38..daf51f7 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= From c58e0d59ca6753da47daa0a64b844bc869030759 Mon Sep 17 00:00:00 2001 From: apriil15 Date: Thu, 5 Jan 2023 10:15:29 +0800 Subject: [PATCH 752/912] docs: update markdown format (#3446) * docs: update markdown format * fix: resolve conflict * docs: update markdown format * docs: update * docs: update * Revert "docs: update" This reverts commit 82716193b753dbcad6fee85973790727b7a31ae5. --- docs/doc.md | 149 +++++++++++++++++++++++++--------------------------- 1 file changed, 73 insertions(+), 76 deletions(-) diff --git a/docs/doc.md b/docs/doc.md index 008a91d..7cebab5 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -450,22 +450,22 @@ func main() { ```go func main() { - // Disable Console Color, you don't need console color when writing the logs to file. - gin.DisableConsoleColor() + // 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) + // 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) + // 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(http.StatusOK, "pong") - }) + router := gin.Default() + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) -    router.Run(":8080") +   router.Run(":8080") } ``` @@ -516,18 +516,18 @@ Never colorize logs: ```go func main() { - // Disable log's color - gin.DisableConsoleColor() + // Disable log's color + gin.DisableConsoleColor() - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) - router.Run(":8080") + router.Run(":8080") } ``` @@ -535,18 +535,18 @@ Always colorize logs: ```go func main() { - // Force log's color - gin.ForceConsoleColor() + // Force log's color + gin.ForceConsoleColor() - // Creates a gin router with default middleware: - // logger and recovery (crash-free) middleware - router := gin.Default() + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() - router.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) - router.Run(":8080") + router.Run(":8080") } ``` @@ -786,11 +786,11 @@ import ( ) type Person struct { - Name string `form:"name"` - Address string `form:"address"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - UnixTime time.Time `form:"unixTime" time_format:"unix"` + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` } func main() { @@ -804,13 +804,13 @@ 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#L88 - if c.ShouldBind(&person) == nil { - log.Println(person.Name) - log.Println(person.Address) - log.Println(person.Birthday) - log.Println(person.CreateTime) - log.Println(person.UnixTime) - } + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + log.Println(person.Birthday) + log.Println(person.CreateTime) + log.Println(person.UnixTime) + } c.String(http.StatusOK, "Success") } @@ -1311,34 +1311,34 @@ main.go ```go import ( - "fmt" - "html/template" - "net/http" - "time" + "fmt" + "html/template" + "net/http" + "time" - "github.com/gin-gonic/gin" + "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) + year, month, day := t.Date() + return fmt.Sprintf("%d/%02d/%02d", year, month, day) } func main() { - router := gin.Default() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("./testdata/template/raw.tmpl") + router := gin.Default() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + router.LoadHTMLFiles("./testdata/template/raw.tmpl") - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", gin.H{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) + router.GET("/raw", func(c *gin.Context) { + c.HTML(http.StatusOK, "raw.tmpl", gin.H{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) + }) - router.Run(":8080") + router.Run(":8080") } ``` @@ -2099,28 +2099,27 @@ func main() { ```go import ( - "fmt" + "fmt" - "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) func main() { + router := gin.Default() - router := gin.Default() - - router.GET("/cookie", func(c *gin.Context) { + router.GET("/cookie", func(c *gin.Context) { - cookie, err := c.Cookie("gin_cookie") + cookie, err := c.Cookie("gin_cookie") - if err != nil { - cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) - } + if err != nil { + cookie = "NotSet" + c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) + } - fmt.Printf("Cookie value: %s \n", cookie) - }) + fmt.Printf("Cookie value: %s \n", cookie) + }) - router.Run() + router.Run() } ``` @@ -2149,7 +2148,6 @@ import ( ) func main() { - router := gin.Default() router.SetTrustedProxies([]string{"192.168.1.2"}) @@ -2176,7 +2174,6 @@ import ( ) func main() { - router := gin.Default() // Use predefined header gin.PlatformXXX router.TrustedPlatform = gin.PlatformGoogleAppEngine From 8eb5f832bac1853fc84c508a2b9406134d39492e Mon Sep 17 00:00:00 2001 From: Kristian Svalland <54534849+kristiansvalland@users.noreply.github.com> Date: Sat, 7 Jan 2023 01:57:54 +0100 Subject: [PATCH 753/912] fix(router): tree bug where loop index is not decremented. (#3460) fixes https://github.com/gin-gonic/gin/issues/3459 --- routes_test.go | 19 +++++++++++++++++++ tree.go | 18 +++++++++--------- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/routes_test.go b/routes_test.go index cd8cf14..ada8e1e 100644 --- a/routes_test.go +++ b/routes_test.go @@ -670,3 +670,22 @@ func TestRouteContextHoldsFullPath(t *testing.T) { w := PerformRequest(router, http.MethodGet, "/not-found") assert.Equal(t, http.StatusNotFound, w.Code) } + +func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) { + r := New() + r.HandleMethodNotAllowed = true + + base := r.Group("base") + base.GET("/metrics", handlerTest1) + + v1 := base.Group("v1") + + v1.GET("/:id/devices", handlerTest1) + v1.GET("/user/:id/groups", handlerTest1) + + v1.GET("/orgs/:id", handlerTest1) + v1.DELETE("/orgs/:id", handlerTest1) + + w := PerformRequest(r, "GET", "/base/v1/user/groups") + assert.Equal(t, http.StatusNotFound, w.Code) +} diff --git a/tree.go b/tree.go index 3f34b8e..dda8f4f 100644 --- a/tree.go +++ b/tree.go @@ -459,9 +459,9 @@ walk: // Outer loop for walking the tree // If the path at the end of the loop is not equal to '/' and the current node has no child nodes // the current node needs to roll back to last valid skippedNode if path != "/" { - for l := len(*skippedNodes); l > 0; { - skippedNode := (*skippedNodes)[l-1] - *skippedNodes = (*skippedNodes)[:l-1] + for length := len(*skippedNodes); length > 0; length-- { + skippedNode := (*skippedNodes)[length-1] + *skippedNodes = (*skippedNodes)[:length-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node @@ -576,9 +576,9 @@ walk: // Outer loop for walking the tree // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node // the current node needs to roll back to last valid skippedNode if n.handlers == nil && path != "/" { - for l := len(*skippedNodes); l > 0; { - skippedNode := (*skippedNodes)[l-1] - *skippedNodes = (*skippedNodes)[:l-1] + for length := len(*skippedNodes); length > 0; length-- { + skippedNode := (*skippedNodes)[length-1] + *skippedNodes = (*skippedNodes)[:length-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node @@ -633,9 +633,9 @@ walk: // Outer loop for walking the tree // roll back to last valid skippedNode if !value.tsr && path != "/" { - for l := len(*skippedNodes); l > 0; { - skippedNode := (*skippedNodes)[l-1] - *skippedNodes = (*skippedNodes)[:l-1] + for length := len(*skippedNodes); length > 0; length-- { + skippedNode := (*skippedNodes)[length-1] + *skippedNodes = (*skippedNodes)[:length-1] if strings.HasSuffix(skippedNode.path, path) { path = skippedNode.path n = skippedNode.node From 47ae6ee386c5b1be402de09c6d69f8d2f0db3c4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 10:11:45 +0800 Subject: [PATCH 754/912] chore(deps): bump golang.org/x/net from 0.4.0 to 0.5.0 (#3466) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8cb21f9..0b1d3a6 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.8 - golang.org/x/net v0.4.0 + golang.org/x/net v0.5.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,6 +30,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect - golang.org/x/sys v0.3.0 // indirect - golang.org/x/text v0.5.0 // indirect + golang.org/x/sys v0.4.0 // indirect + golang.org/x/text v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index daf51f7..f86942b 100644 --- a/go.sum +++ b/go.sum @@ -73,20 +73,20 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 3010cbd7f4eccdbb610c510274895e083b8c058c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jan 2023 10:12:12 +0800 Subject: [PATCH 755/912] chore(deps): bump github.com/bytedance/sonic from 1.6.0 to 1.6.1 (#3467) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.6.0 to 1.6.1. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.6.0...v1.6.1) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0b1d3a6..c498b48 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.6.0 + github.com/bytedance/sonic v1.6.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.1 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index f86942b..409d393 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.6.0 h1:j90DM/Ss1bmySEQYL2U4jRsUjJ+chASzCCZYxohJR60= -github.com/bytedance/sonic v1.6.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.6.1 h1:HEyWqlvEh95R/rMg5Mh6jDx5Zt35MG24QWzpHMVuan0= +github.com/bytedance/sonic v1.6.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From 7cb151bb4c4cfc6018a00a125422ff38a041b9f8 Mon Sep 17 00:00:00 2001 From: adrianiacobghiula <2491756+adrianiacobghiula@users.noreply.github.com> Date: Mon, 16 Jan 2023 15:50:07 +0100 Subject: [PATCH 756/912] fix(context): panic on NegotiateFormat - index out of range (#3397) --- context.go | 2 +- context_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index b1352b9..0474252 100644 --- a/context.go +++ b/context.go @@ -1147,7 +1147,7 @@ func (c *Context) NegotiateFormat(offered ...string) string { // According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers, // therefore we can just iterate over the string without casting it into []rune i := 0 - for ; i < len(accepted); i++ { + for ; i < len(accepted) && i < len(offer); i++ { if accepted[i] == '*' || offer[i] == '*' { return offer } diff --git a/context_test.go b/context_test.go index 85e0a61..827ee0f 100644 --- a/context_test.go +++ b/context_test.go @@ -1311,6 +1311,14 @@ func TestContextNegotiationFormatCustom(t *testing.T) { assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) } +func TestContextNegotiationFormat2(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("Accept", "image/tiff-fx") + + assert.Equal(t, "", c.NegotiateFormat("image/tiff")) +} + func TestContextIsAborted(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.False(t, c.IsAborted()) From 97082f8accd197ec1240b31df3a20993c3747e6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jan 2023 09:58:28 +0800 Subject: [PATCH 757/912] chore(deps): bump github.com/bytedance/sonic from 1.6.1 to 1.7.0 (#3473) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.6.1 to 1.7.0. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.6.1...v1.7.0) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c498b48..e03001e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.6.1 + github.com/bytedance/sonic v1.7.0 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.1 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index 409d393..4e4e963 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.6.1 h1:HEyWqlvEh95R/rMg5Mh6jDx5Zt35MG24QWzpHMVuan0= -github.com/bytedance/sonic v1.6.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.7.0 h1:P7DyGrkLbVDzcuqagPsSFnAwwljjhmB3qVF5wzmHOxE= +github.com/bytedance/sonic v1.7.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From 1660995a04f579b4e0d5683ff45e1af4c2a50346 Mon Sep 17 00:00:00 2001 From: Heliner <32272517+Heliner@users.noreply.github.com> Date: Tue, 17 Jan 2023 14:23:54 +0800 Subject: [PATCH 758/912] Adjust the position of some functions (#3385) Co-authored-by: fredhan --- binding/toml.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/binding/toml.go b/binding/toml.go index a6b8a90..a66b93a 100644 --- a/binding/toml.go +++ b/binding/toml.go @@ -18,14 +18,6 @@ func (tomlBinding) Name() string { return "toml" } -func decodeToml(r io.Reader, obj any) error { - decoder := toml.NewDecoder(r) - if err := decoder.Decode(obj); err != nil { - return err - } - return decoder.Decode(obj) -} - func (tomlBinding) Bind(req *http.Request, obj any) error { return decodeToml(req.Body, obj) } @@ -33,3 +25,11 @@ func (tomlBinding) Bind(req *http.Request, obj any) error { func (tomlBinding) BindBody(body []byte, obj any) error { return decodeToml(bytes.NewReader(body), obj) } + +func decodeToml(r io.Reader, obj any) error { + decoder := toml.NewDecoder(r) + if err := decoder.Decode(obj); err != nil { + return err + } + return decoder.Decode(obj) +} From 8cd11c82e447f74d63e3da6037cb0463440d8e16 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Tue, 17 Jan 2023 14:26:27 +0800 Subject: [PATCH 759/912] chore(docs): Remove the Brigade project, because the Gin is no longer used in the latest version and the Brigade is an archived CNCF project now (#3378) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index eccf814..336155a 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,6 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. * [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. -* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. From b2d4185eec36ce5e0cf21be7cb246fb8be9fd6db Mon Sep 17 00:00:00 2001 From: hopehook Date: Fri, 20 Jan 2023 09:51:42 +0800 Subject: [PATCH 760/912] Replace bytes.Buffer with strings.Builder where appropriate (#3347) To build strings more efficiently, use strings.Builder instead. --- debug_test.go | 4 ++-- githubapi_test.go | 4 ++-- logger_test.go | 14 +++++++------- recovery_test.go | 19 +++++++++---------- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/debug_test.go b/debug_test.go index abe8b41..ce8b19d 100644 --- a/debug_test.go +++ b/debug_test.go @@ -5,7 +5,6 @@ package gin import ( - "bytes" "errors" "fmt" "html/template" @@ -13,6 +12,7 @@ import ( "log" "os" "runtime" + "strings" "sync" "testing" @@ -138,7 +138,7 @@ func captureOutput(t *testing.T, f func()) string { wg := new(sync.WaitGroup) wg.Add(1) go func() { - var buf bytes.Buffer + var buf strings.Builder wg.Done() _, err := io.Copy(&buf, reader) assert.NoError(t, err) diff --git a/githubapi_test.go b/githubapi_test.go index c6350e8..9276bed 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -5,12 +5,12 @@ package gin import ( - "bytes" "fmt" "math/rand" "net/http" "net/http/httptest" "os" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -401,7 +401,7 @@ func TestGithubAPI(t *testing.T) { } func exampleFromPath(path string) (string, Params) { - output := new(bytes.Buffer) + output := new(strings.Builder) params := make(Params, 0, 6) start := -1 for i, c := range path { diff --git a/logger_test.go b/logger_test.go index 7bc1137..5f78708 100644 --- a/logger_test.go +++ b/logger_test.go @@ -5,10 +5,10 @@ package gin import ( - "bytes" "errors" "fmt" "net/http" + "strings" "testing" "time" @@ -20,7 +20,7 @@ func init() { } func TestLogger(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(LoggerWithWriter(buffer)) router.GET("/example", func(c *Context) {}) @@ -84,7 +84,7 @@ func TestLogger(t *testing.T) { } func TestLoggerWithConfig(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(LoggerWithConfig(LoggerConfig{Output: buffer})) router.GET("/example", func(c *Context) {}) @@ -148,7 +148,7 @@ func TestLoggerWithConfig(t *testing.T) { } func TestLoggerWithFormatter(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) d := DefaultWriter DefaultWriter = buffer @@ -182,7 +182,7 @@ func TestLoggerWithFormatter(t *testing.T) { func TestLoggerWithConfigFormatting(t *testing.T) { var gotParam LogFormatterParams var gotKeys map[string]any - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.engine.trustedCIDRs, _ = router.engine.prepareTrustedCIDRs() @@ -382,7 +382,7 @@ func TestErrorLogger(t *testing.T) { } func TestLoggerWithWriterSkippingPaths(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(LoggerWithWriter(buffer, "/skipped")) router.GET("/logged", func(c *Context) {}) @@ -397,7 +397,7 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) { } func TestLoggerWithConfigSkippingPaths(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(LoggerWithConfig(LoggerConfig{ Output: buffer, diff --git a/recovery_test.go b/recovery_test.go index 347917e..fa8ab89 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -5,7 +5,6 @@ package gin import ( - "bytes" "fmt" "net" "net/http" @@ -18,7 +17,7 @@ import ( ) func TestPanicClean(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() password := "my-super-secret-password" router.Use(RecoveryWithWriter(buffer)) @@ -50,7 +49,7 @@ func TestPanicClean(t *testing.T) { // TestPanicInHandler assert that panic has been recovered. func TestPanicInHandler(t *testing.T) { - buffer := new(bytes.Buffer) + buffer := new(strings.Builder) router := New() router.Use(RecoveryWithWriter(buffer)) router.GET("/recovery", func(_ *Context) { @@ -122,7 +121,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { for errno, expectMsg := range expectMsgs { t.Run(expectMsg, func(t *testing.T) { - var buf bytes.Buffer + var buf strings.Builder router := New() router.Use(RecoveryWithWriter(&buf)) @@ -145,8 +144,8 @@ func TestPanicWithBrokenPipe(t *testing.T) { } func TestCustomRecoveryWithWriter(t *testing.T) { - errBuffer := new(bytes.Buffer) - buffer := new(bytes.Buffer) + errBuffer := new(strings.Builder) + buffer := new(strings.Builder) router := New() handleRecovery := func(c *Context, err any) { errBuffer.WriteString(err.(string)) @@ -179,8 +178,8 @@ func TestCustomRecoveryWithWriter(t *testing.T) { } func TestCustomRecovery(t *testing.T) { - errBuffer := new(bytes.Buffer) - buffer := new(bytes.Buffer) + errBuffer := new(strings.Builder) + buffer := new(strings.Builder) router := New() DefaultErrorWriter = buffer handleRecovery := func(c *Context, err any) { @@ -214,8 +213,8 @@ func TestCustomRecovery(t *testing.T) { } func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { - errBuffer := new(bytes.Buffer) - buffer := new(bytes.Buffer) + errBuffer := new(strings.Builder) + buffer := new(strings.Builder) router := New() DefaultErrorWriter = buffer handleRecovery := func(c *Context, err any) { From ea1787503586f94d7d79323573d35eb3f442a561 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 19:53:10 +0800 Subject: [PATCH 761/912] chore(deps): bump golangci/golangci-lint-action from 3.3.1 to 3.4.0 (#3478) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3.3.1 to 3.4.0. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3.3.1...v3.4.0) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 7a4e61c..9bd2698 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.3.1 + uses: golangci/golangci-lint-action@v3.4.0 with: version: v1.48.0 args: --verbose From c5fd06361b934070e99978c34e1eaef05632bb5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 19:53:45 +0800 Subject: [PATCH 762/912] chore(deps): bump github.com/go-playground/validator/v10 (#3482) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.11.1 to 10.11.2. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.11.1...v10.11.2) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 9 +++++---- go.sum | 40 +++++++++------------------------------- 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index e03001e..ea2c94a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/bytedance/sonic v1.7.0 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.11.1 + github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.17 @@ -20,16 +20,17 @@ require ( require ( github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.0.14 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect + golang.org/x/crypto v0.5.0 // indirect golang.org/x/sys v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect ) diff --git a/go.sum b/go.sum index 4e4e963..09341d4 100644 --- a/go.sum +++ b/go.sum @@ -10,14 +10,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -29,12 +28,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= @@ -47,12 +41,9 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -70,36 +61,23 @@ github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZg golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From e02ae6ae61fada360379b5bdc7f23e46f21ce5de Mon Sep 17 00:00:00 2001 From: "Alireza (Pure)" Date: Mon, 6 Feb 2023 11:16:42 +0330 Subject: [PATCH 763/912] chore(router): match method added to routergroup for multiple HTTP methods supporting (#3464) --- routergroup.go | 10 ++++++++++ routergroup_test.go | 1 + 2 files changed, 11 insertions(+) diff --git a/routergroup.go b/routergroup.go index dfbdd7b..c833fe8 100644 --- a/routergroup.go +++ b/routergroup.go @@ -42,6 +42,7 @@ type IRoutes interface { PUT(string, ...HandlerFunc) IRoutes OPTIONS(string, ...HandlerFunc) IRoutes HEAD(string, ...HandlerFunc) IRoutes + Match([]string, string, ...HandlerFunc) IRoutes StaticFile(string, string) IRoutes StaticFileFS(string, string, http.FileSystem) IRoutes @@ -151,6 +152,15 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou return group.returnObj() } +// Match registers a route that matches the specified methods that you declared. +func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes { + for _, method := range methods { + group.handle(method, relativePath, handlers) + } + + return group.returnObj() +} + // StaticFile registers a single route in order to serve a single file of the local filesystem. // router.StaticFile("favicon.ico", "./resources/favicon.ico") func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { diff --git a/routergroup_test.go b/routergroup_test.go index 41f9637..6848063 100644 --- a/routergroup_test.go +++ b/routergroup_test.go @@ -186,6 +186,7 @@ func testRoutesInterface(t *testing.T, r IRoutes) { assert.Equal(t, r, r.PUT("/", handler)) assert.Equal(t, r, r.OPTIONS("/", handler)) assert.Equal(t, r, r.HEAD("/", handler)) + assert.Equal(t, r, r.Match([]string{http.MethodPut, http.MethodPatch}, "/match", handler)) assert.Equal(t, r, r.StaticFile("/file", ".")) assert.Equal(t, r, r.StaticFileFS("/static2", ".", Dir(".", false))) From 153b229fcc6570bac0674d02ab1a629804f29072 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 16:37:36 +0800 Subject: [PATCH 764/912] chore(deps): bump github.com/ugorji/go/codec from 1.2.8 to 1.2.9 (#3491) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.2.8 to 1.2.9. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/v1.2.8...v1.2.9) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ea2c94a..4ec5325 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 - github.com/ugorji/go/codec v1.2.8 + github.com/ugorji/go/codec v1.2.9 golang.org/x/net v0.5.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 09341d4..b2d2f73 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0= -github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= +github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= From 0c96a20209ca035964be126a745c167196fb6db3 Mon Sep 17 00:00:00 2001 From: Vladislav Dmitriyev Date: Sun, 12 Feb 2023 05:01:33 +0300 Subject: [PATCH 765/912] Stop useless panicking in context and render (#2150) Co-authored-by: Bo-Yi Wu --- context.go | 4 +++- context_test.go | 20 +++++++++----------- render/json.go | 7 ++----- render/render_test.go | 4 ++-- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/context.go b/context.go index 0474252..556f8ac 100644 --- a/context.go +++ b/context.go @@ -924,7 +924,9 @@ func (c *Context) Render(code int, r render.Render) { } if err := r.Render(c.Writer); err != nil { - panic(err) + // Pushing error to c.Errors + _ = c.Error(err) + c.Abort() } } diff --git a/context_test.go b/context_test.go index 827ee0f..1ab6b33 100644 --- a/context_test.go +++ b/context_test.go @@ -32,6 +32,8 @@ import ( var _ context.Context = (*Context)(nil) +var errTestRender = errors.New("TestRender") + // Unit tests TODO // func (c *Context) File(filepath string) { // func (c *Context) Negotiate(code int, config Negotiate) { @@ -643,25 +645,21 @@ func TestContextBodyAllowedForStatus(t *testing.T) { assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError)) } -type TestPanicRender struct{} +type TestRender struct{} -func (*TestPanicRender) Render(http.ResponseWriter) error { - return errors.New("TestPanicRender") +func (*TestRender) Render(http.ResponseWriter) error { + return errTestRender } -func (*TestPanicRender) WriteContentType(http.ResponseWriter) {} +func (*TestRender) WriteContentType(http.ResponseWriter) {} -func TestContextRenderPanicIfErr(t *testing.T) { - defer func() { - r := recover() - assert.Equal(t, "TestPanicRender", fmt.Sprint(r)) - }() +func TestContextRenderIfErr(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Render(http.StatusOK, &TestPanicRender{}) + c.Render(http.StatusOK, &TestRender{}) - assert.Fail(t, "Panic not detected") + assert.Equal(t, errorMsgs{&Error{Err: errTestRender, Type: 1}}, c.Errors) } // Tests that the response is serialized as JSON diff --git a/render/json.go b/render/json.go index af678e8..fc8dea4 100644 --- a/render/json.go +++ b/render/json.go @@ -53,11 +53,8 @@ var ( ) // Render (JSON) writes data with custom ContentType. -func (r JSON) Render(w http.ResponseWriter) (err error) { - if err = WriteJSON(w, r.Data); err != nil { - panic(err) - } - return +func (r JSON) Render(w http.ResponseWriter) error { + return WriteJSON(w, r.Data) } // WriteContentType (JSON) writes JSON ContentType. diff --git a/render/render_test.go b/render/render_test.go index ebb7d41..1925525 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -40,12 +40,12 @@ func TestRenderJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } -func TestRenderJSONPanics(t *testing.T) { +func TestRenderJSONError(t *testing.T) { w := httptest.NewRecorder() data := make(chan int) // json: unsupported type: chan int - assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) }) + assert.Error(t, (JSON{data}).Render(w)) } func TestRenderIndentedJSON(t *testing.T) { From bd82c9e351be91e9e8267e5ce011627dd6c55d51 Mon Sep 17 00:00:00 2001 From: mstmdev Date: Sun, 12 Feb 2023 13:01:05 +0800 Subject: [PATCH 766/912] =?UTF-8?q?chore(go):=20Add=C2=A0support=20go=201.?= =?UTF-8?q?20=20(#3484)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(go): Add support go 1.20 * Surround the go version parameters with single quotes * chore(deps): bump github.com/bytedance/sonic from v1.7.0 to v1.7.1 --- .github/workflows/gin.yml | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 9bd2698..fac97d4 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -31,7 +31,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: [1.16, 1.17, 1.18, 1.19] + go: ['1.16', '1.17', '1.18', '1.19', '1.20'] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest diff --git a/go.mod b/go.mod index 4ec5325..51353f5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.7.0 + github.com/bytedance/sonic v1.7.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index b2d2f73..01f9495 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.7.0 h1:P7DyGrkLbVDzcuqagPsSFnAwwljjhmB3qVF5wzmHOxE= -github.com/bytedance/sonic v1.7.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.7.1 h1:UYWEKUHQDye89c2U6zvrvuxWdGCI/wCrZITFQmKGtGc= +github.com/bytedance/sonic v1.7.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From c1d06e3d08692f9eddde381a5e277b41fff5a297 Mon Sep 17 00:00:00 2001 From: David Desmarais-Michaud Date: Sun, 12 Feb 2023 00:01:43 -0500 Subject: [PATCH 767/912] add supprt for go1.20 http.rwUnwrapper to gin.responseWriter (#3489) --- response_writer.go | 4 ++++ response_writer_test.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/response_writer.go b/response_writer.go index 5cec1f6..753a0b0 100644 --- a/response_writer.go +++ b/response_writer.go @@ -51,6 +51,10 @@ type responseWriter struct { var _ ResponseWriter = (*responseWriter)(nil) +func (w *responseWriter) Unwrap() http.ResponseWriter { + return w.ResponseWriter +} + func (w *responseWriter) reset(writer http.ResponseWriter) { w.ResponseWriter = writer w.size = noWritten diff --git a/response_writer_test.go b/response_writer_test.go index 6fa5ec7..9fd5e87 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -30,6 +30,12 @@ func init() { SetMode(TestMode) } +func TestResponseWriterUnwrap(t *testing.T) { + testWriter := httptest.NewRecorder() + writer := &responseWriter{ResponseWriter: testWriter} + assert.Same(t, testWriter, writer.Unwrap()) +} + func TestResponseWriterReset(t *testing.T) { testWriter := httptest.NewRecorder() writer := &responseWriter{} From d07db174acf44bfaf191ca2f6d7beafa2ff946da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Feb 2023 10:59:36 +0800 Subject: [PATCH 768/912] chore(deps): bump golang.org/x/net from 0.5.0 to 0.6.0 (#3498) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.6.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.5.0...v0.6.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 51353f5..2ef45cf 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.9 - golang.org/x/net v0.5.0 + golang.org/x/net v0.6.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -31,6 +31,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect golang.org/x/crypto v0.5.0 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/text v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 01f9495..b47804f 100644 --- a/go.sum +++ b/go.sum @@ -63,13 +63,13 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SX golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= From 81ac7d55a09e34013225db0aeac6e70c1ae68928 Mon Sep 17 00:00:00 2001 From: t0rchwo0d Date: Fri, 17 Feb 2023 11:00:19 +0900 Subject: [PATCH 769/912] Add escape logic for header (#3500) --- gin.go | 4 ++++ routes_test.go | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/gin.go b/gin.go index 35159d0..32dae24 100644 --- a/gin.go +++ b/gin.go @@ -9,6 +9,7 @@ import ( "html/template" "net" "net/http" + "net/url" "os" "path" "strings" @@ -668,6 +669,9 @@ func redirectTrailingSlash(c *Context) { req := c.Request p := req.URL.Path if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { + prefix = url.QueryEscape(prefix) + prefix = strings.ReplaceAll(prefix, "%2F", "/") + p = prefix + "/" + req.URL.Path } req.URL.Path = p + "/" diff --git a/routes_test.go b/routes_test.go index ada8e1e..5310cae 100644 --- a/routes_test.go +++ b/routes_test.go @@ -185,6 +185,18 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) assert.Equal(t, 200, w.Code) + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../bug#?"}) + assert.Equal(t, "../../../bug%2523%253F/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"}) + assert.Equal(t, "https%3A/gin-gonic.com/%23/https%253A/gin-gonic.com/%2523/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#bug"}) + assert.Equal(t, "%23bug/%2523bug/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + router.RedirectTrailingSlash = false w = PerformRequest(router, http.MethodGet, "/path/") From fc1c43298de675e5252d0b44f97dc5e204bd4e1e Mon Sep 17 00:00:00 2001 From: Kevin Chen Date: Sat, 18 Feb 2023 01:43:39 -0500 Subject: [PATCH 770/912] fix(security): vulnerability GO-2023-1571 (#3505) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2ef45cf..f7e28d8 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 github.com/ugorji/go/codec v1.2.9 - golang.org/x/net v0.6.0 + golang.org/x/net v0.7.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index b47804f..814f4eb 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,8 @@ golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SX golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 4cee78f5382d5245c3652e6c15fee715eec505c3 Mon Sep 17 00:00:00 2001 From: t0rchwo0d Date: Sun, 19 Feb 2023 22:25:48 +0900 Subject: [PATCH 771/912] Fix #3500 Add escape logic for header (#3503) --- gin.go | 9 ++++++--- routes_test.go | 46 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/gin.go b/gin.go index 32dae24..f95e5dd 100644 --- a/gin.go +++ b/gin.go @@ -9,9 +9,9 @@ import ( "html/template" "net" "net/http" - "net/url" "os" "path" + "regexp" "strings" "sync" @@ -41,6 +41,9 @@ var defaultTrustedCIDRs = []*net.IPNet{ }, } +var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+") +var regRemoveRepeatedChar = regexp.MustCompile("/{2,}") + // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) @@ -669,8 +672,8 @@ func redirectTrailingSlash(c *Context) { req := c.Request p := req.URL.Path if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { - prefix = url.QueryEscape(prefix) - prefix = strings.ReplaceAll(prefix, "%2F", "/") + prefix = regSafePrefix.ReplaceAllString(prefix, "") + prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/") p = prefix + "/" + req.URL.Path } diff --git a/routes_test.go b/routes_test.go index 5310cae..633c0ab 100644 --- a/routes_test.go +++ b/routes_test.go @@ -185,16 +185,52 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) assert.Equal(t, 200, w.Code) - w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../bug#?"}) - assert.Equal(t, "../../../bug%2523%253F/path", w.Header().Get("Location")) + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api#?"}) + assert.Equal(t, "/api/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) + assert.Equal(t, "/api/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) + assert.Equal(t, "/api/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../api"}) + assert.Equal(t, "/api/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../"}) + assert.Equal(t, "//path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../../"}) + assert.Equal(t, "/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../gin-gonic.com"}) + assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../gin-gonic.com"}) + assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) assert.Equal(t, 301, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"}) - assert.Equal(t, "https%3A/gin-gonic.com/%23/https%253A/gin-gonic.com/%2523/path", w.Header().Get("Location")) + assert.Equal(t, "https/gin-goniccom/https/gin-goniccom/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#api"}) + assert.Equal(t, "api/api/path", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/#?a=1"}) + assert.Equal(t, "/nor-mal/a1/path", w.Header().Get("Location")) assert.Equal(t, 301, w.Code) - w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#bug"}) - assert.Equal(t, "%23bug/%2523bug/path", w.Header().Get("Location")) + w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/%2e%2e/"}) + assert.Equal(t, "/nor-mal/2e2e/path", w.Header().Get("Location")) assert.Equal(t, 301, w.Code) router.RedirectTrailingSlash = false From ea03e10384502e1baf6f560a2b0ea32c342ede5b Mon Sep 17 00:00:00 2001 From: thinkerou Date: Tue, 21 Feb 2023 17:20:32 +0800 Subject: [PATCH 772/912] docs(readme): release v1.9.0 version (#3474) --- CHANGELOG.md | 79 ++++++++++++++++++++++++++++++++++++++++++---------- go.mod | 6 ++-- go.sum | 10 +++---- version.go | 2 +- 4 files changed, 73 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ab5617..cf24ec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,49 @@ # Gin ChangeLog +## Gin v1.9.0 + +### BREAK CHANGES + +* Stop useless panicking in context and render [#2150](https://github.com/gin-gonic/gin/pull/2150) + +### BUG FIXES + +* fix(router): tree bug where loop index is not decremented. [#3460](https://github.com/gin-gonic/gin/pull/3460) +* fix(context): panic on NegotiateFormat - index out of range [#3397](https://github.com/gin-gonic/gin/pull/3397) +* Add escape logic for header [#3500](https://github.com/gin-gonic/gin/pull/3500) and [#3503](https://github.com/gin-gonic/gin/pull/3503) + +### SECURITY + +* Fix the GO-2022-0969 and GO-2022-0288 vulnerabilities [#3333](https://github.com/gin-gonic/gin/pull/3333) +* fix(security): vulnerability GO-2023-1571 [#3505](https://github.com/gin-gonic/gin/pull/3505) + +### ENHANCEMENTS + +* feat: add sonic json support [#3184](https://github.com/gin-gonic/gin/pull/3184) +* chore(file): Creates a directory named path [#3316](https://github.com/gin-gonic/gin/pull/3316) +* fix: modify interface check way [#3327](https://github.com/gin-gonic/gin/pull/3327) +* remove deprecated of package io/ioutil [#3395](https://github.com/gin-gonic/gin/pull/3395) +* refactor: avoid calling strings.ToLower twice [#3343](https://github.com/gin-gonic/gin/pull/3433) +* console logger HTTP status code bug fixed [#3453](https://github.com/gin-gonic/gin/pull/3453) +* chore(yaml): upgrade dependency to v3 version [#3456](https://github.com/gin-gonic/gin/pull/3456) +* chore(router): match method added to routergroup for multiple HTTP methods supporting [#3464](https://github.com/gin-gonic/gin/pull/3464) +* chore(http): add support for go1.20 http.rwUnwrapper to gin.responseWriter [#3489](https://github.com/gin-gonic/gin/pull/3489) + +### DOCS + +* docs: update markdown format [#3260](https://github.com/gin-gonic/gin/pull/3260) +* docs(readme): Add the TOML rendering example [#3400](https://github.com/gin-gonic/gin/pull/3400) +* docs(readme): move more example to docs/doc.md [#3449](https://github.com/gin-gonic/gin/pull/3449) +* docs: update markdown format [#3446](https://github.com/gin-gonic/gin/pull/3446) + ## Gin v1.8.2 -### Bugs +### BUG FIXES * fix(route): redirectSlash bug ([#3227]((https://github.com/gin-gonic/gin/pull/3227))) * fix(engine): missing route params for CreateTestContext ([#2778]((https://github.com/gin-gonic/gin/pull/2778))) ([#2803]((https://github.com/gin-gonic/gin/pull/2803))) -### Security +### SECURITY * Fix the GO-2022-1144 vulnerability ([#3432]((https://github.com/gin-gonic/gin/pull/3432))) @@ -19,12 +55,12 @@ ## Gin v1.8.0 -## Break Changes +### BREAK CHANGES * TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967). Please replace `RemoteIP() (net.IP, bool)` with `RemoteIP() net.IP` * gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751) -### BUGFIXES +### BUG FIXES * Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861) * Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983) @@ -61,7 +97,7 @@ ## Gin v1.7.7 -### BUGFIXES +### BUG FIXES * Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862). * Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878). @@ -79,37 +115,37 @@ ## Gin v1.7.6 -### BUGFIXES +### BUG FIXES * bump new release to fix v1.7.5 release error by using v1.7.4 codes. ## Gin v1.7.4 -### BUGFIXES +### BUG FIXES * bump new release to fix checksum mismatch ## Gin v1.7.3 -### BUGFIXES +### BUG FIXES * fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796) ## Gin v1.7.2 -### BUGFIXES +### BUG FIXES * Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696). ## Gin v1.7.1 -### BUGFIXES +### BUG FIXES * fix: data race with trustedCIDRs from [#2674](https://github.com/gin-gonic/gin/issues/2674)([#2675](https://github.com/gin-gonic/gin/pull/2675)) ## Gin v1.7.0 -### BUGFIXES +### BUG FIXES * fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600)) * fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528)) @@ -148,33 +184,44 @@ ## Gin v1.6.2 -### BUGFIXES +### BUG FIXES + * fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305) + ### ENHANCEMENTS + * Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306) ## Gin v1.6.1 -### BUGFIXES +### BUG FIXES + * Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294) ## Gin v1.6.0 ### BREAKING + * chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159) * drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148) * Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615) + ### FEATURES + * add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220) * FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112) -### BUGFIXES + +### BUG FIXES + * Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280) * Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228) * fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216) * Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166) * [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121) * Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391) + ### ENHANCEMENTS + * Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277) * tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229) * tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222) @@ -189,7 +236,9 @@ * upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149) * Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970) * Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852) + ### DOCS + * docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223) * Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217) * Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202) @@ -202,7 +251,9 @@ * Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165) * docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153) * Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122) + ### MISC + * ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262) * chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231) * Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147) diff --git a/go.mod b/go.mod index f7e28d8..db36337 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.7.1 + github.com/bytedance/sonic v1.8.0 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 @@ -22,14 +22,14 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/klauspost/cpuid/v2 v2.0.14 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.5.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect diff --git a/go.sum b/go.sum index 814f4eb..8bdb934 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.7.1 h1:UYWEKUHQDye89c2U6zvrvuxWdGCI/wCrZITFQmKGtGc= -github.com/bytedance/sonic v1.7.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= @@ -25,9 +25,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8= -github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -58,9 +57,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU= -golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= diff --git a/version.go b/version.go index 37e27f2..390da4f 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.8.2" +const Version = "v1.9.0" From 0b5df9fc3992bde6e13fd71b795ff4f8b27d4f65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:42:49 +0800 Subject: [PATCH 773/912] chore(deps): bump github.com/bytedance/sonic from 1.7.1 to 1.8.1 (#3508) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.7.1 to 1.8.1. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.7.1...v1.8.1) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index db36337..3ec4780 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.8.0 + github.com/bytedance/sonic v1.8.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index 8bdb934..d6a9193 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= -github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.8.1 h1:NqAHCaGaTzro0xMmnTCLUyRlbEP6r8MCA1cJUrH3Pu4= +github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From 943e93cba04808294d0748b74bcdc8322b8ebaa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:43:24 +0800 Subject: [PATCH 774/912] chore(deps): bump github.com/ugorji/go/codec from 1.2.9 to 1.2.10 (#3509) Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.2.9 to 1.2.10. - [Release notes](https://github.com/ugorji/go/releases) - [Commits](https://github.com/ugorji/go/compare/v1.2.9...v1.2.10) --- updated-dependencies: - dependency-name: github.com/ugorji/go/codec dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3ec4780..da97874 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/mattn/go-isatty v0.0.17 github.com/pelletier/go-toml/v2 v2.0.6 github.com/stretchr/testify v1.8.1 - github.com/ugorji/go/codec v1.2.9 + github.com/ugorji/go/codec v1.2.10 golang.org/x/net v0.7.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index d6a9193..cab49ab 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= -github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= +github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= From 1e1f0b1e76b89b48542171e2c5ee829a69c2b91f Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 1 Mar 2023 10:03:48 +0800 Subject: [PATCH 775/912] chore: support min go version 1.18 (#3511) * chore: min go version 1.18 * fix build tag error * remove build tag * fix word * remove any.go * replace interface{} instead of any --- .github/workflows/gin.yml | 6 +- .github/workflows/goreleaser.yml | 2 +- README.md | 2 +- any.go | 10 --- binding/any.go | 10 --- binding/binding.go | 1 - binding/binding_msgpack_test.go | 1 - binding/binding_nomsgpack.go | 1 - binding/json.go | 2 +- binding/msgpack.go | 1 - binding/msgpack_test.go | 1 - context.go | 6 +- context_1.17_test.go | 94 -------------------- context_1.16_test.go => context_1.18_test.go | 20 +++-- context_1.19_test.go | 1 - context_appengine.go | 1 - context_test.go | 41 ++++++++- debug.go | 4 +- debug_test.go | 4 +- deprecated.go | 2 +- docs/doc.md | 8 +- internal/json/go_json.go | 1 - internal/json/json.go | 3 - internal/json/jsoniter.go | 1 - internal/json/sonic.go | 4 - render/any.go | 10 --- render/msgpack.go | 1 - render/render_msgpack_test.go | 1 - testdata/protoexample/any.go | 10 --- utils.go | 2 +- 30 files changed, 72 insertions(+), 179 deletions(-) delete mode 100644 any.go delete mode 100644 binding/any.go delete mode 100644 context_1.17_test.go rename context_1.16_test.go => context_1.18_test.go (66%) delete mode 100644 render/any.go delete mode 100644 testdata/protoexample/any.go diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index fac97d4..5c1504a 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -18,7 +18,7 @@ jobs: - name: Setup go uses: actions/setup-go@v3 with: - go-version: '^1.16' + go-version: '^1.18' - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint @@ -31,7 +31,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ['1.16', '1.17', '1.18', '1.19', '1.20'] + go: ['1.18', '1.19', '1.20'] test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] include: - os: ubuntu-latest @@ -73,7 +73,7 @@ jobs: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - name: Format - if: matrix.go-version == '1.19.x' + if: matrix.go-version == '1.20.x' run: diff -u <(echo -n) <(gofmt -d .) notification-gitter: needs: test diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 3af3a45..baf02af 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -21,7 +21,7 @@ jobs: name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.17 + go-version: 1.20 - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 diff --git a/README.md b/README.md index 336155a..cba54ab 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Gin is a web framework written in [Go](https://go.dev/). It features a martini-l ### Prerequisites -- **[Go](https://go.dev/)**: ~~any one of the **three latest major** [releases](https://go.dev/doc/devel/release)~~ (now version **1.16+** is required). +- **[Go](https://go.dev/)**: any one of the **three latest major** [releases](https://go.dev/doc/devel/release) (we test it with these). ### Getting Gin diff --git a/any.go b/any.go deleted file mode 100644 index 42b1ea4..0000000 --- a/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package gin - -type any = interface{} diff --git a/binding/any.go b/binding/any.go deleted file mode 100644 index d8251a7..0000000 --- a/binding/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package binding - -type any = interface{} diff --git a/binding/binding.go b/binding/binding.go index a58924e..4094852 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go index 04d9407..a6cd6aa 100644 --- a/binding/binding_msgpack_test.go +++ b/binding/binding_msgpack_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index 7f6a904..93ad8ba 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build nomsgpack -// +build nomsgpack package binding diff --git a/binding/json.go b/binding/json.go index 36eb27a..e21c2ee 100644 --- a/binding/json.go +++ b/binding/json.go @@ -15,7 +15,7 @@ import ( // EnableDecoderUseNumber is used to call the UseNumber method on the JSON // Decoder instance. UseNumber causes the Decoder to unmarshal a number into an -// interface{} as a Number instead of as a float64. +// any as a Number instead of as a float64. var EnableDecoderUseNumber = false // EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method diff --git a/binding/msgpack.go b/binding/msgpack.go index d1f035e..22de9b5 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go index 11561c8..df386a6 100644 --- a/binding/msgpack_test.go +++ b/binding/msgpack_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package binding diff --git a/context.go b/context.go index 556f8ac..5716318 100644 --- a/context.go +++ b/context.go @@ -652,7 +652,7 @@ func (c *Context) BindYAML(obj any) error { } // BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML). -func (c *Context) BindTOML(obj interface{}) error { +func (c *Context) BindTOML(obj any) error { return c.MustBindWith(obj, binding.TOML) } @@ -717,7 +717,7 @@ func (c *Context) ShouldBindYAML(obj any) error { } // ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML). -func (c *Context) ShouldBindTOML(obj interface{}) error { +func (c *Context) ShouldBindTOML(obj any) error { return c.ShouldBindWith(obj, binding.TOML) } @@ -995,7 +995,7 @@ func (c *Context) YAML(code int, obj any) { } // TOML serializes the given struct as TOML into the response body. -func (c *Context) TOML(code int, obj interface{}) { +func (c *Context) TOML(code int, obj any) { c.Render(code, render.TOML{Data: obj}) } diff --git a/context_1.17_test.go b/context_1.17_test.go deleted file mode 100644 index 0f8527f..0000000 --- a/context_1.17_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build go1.17 -// +build go1.17 - -package gin - -import ( - "bytes" - "mime/multipart" - "net/http" - "net/http/httptest" - "runtime" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -type interceptedWriter struct { - ResponseWriter - b *bytes.Buffer -} - -func (i interceptedWriter) WriteHeader(code int) { - i.Header().Del("X-Test") - i.ResponseWriter.WriteHeader(code) -} - -func TestContextFormFileFailed17(t *testing.T) { - if !isGo117OrGo118() { - return - } - buf := new(bytes.Buffer) - mw := multipart.NewWriter(buf) - defer func(mw *multipart.Writer) { - err := mw.Close() - if err != nil { - assert.Error(t, err) - } - }(mw) - c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Set("Content-Type", mw.FormDataContentType()) - c.engine.MaxMultipartMemory = 8 << 20 - assert.Panics(t, func() { - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) - }) -} - -func TestInterceptedHeader(t *testing.T) { - w := httptest.NewRecorder() - c, r := CreateTestContext(w) - - r.Use(func(c *Context) { - i := interceptedWriter{ - ResponseWriter: c.Writer, - b: bytes.NewBuffer(nil), - } - c.Writer = i - c.Next() - c.Header("X-Test", "overridden") - c.Writer = i.ResponseWriter - }) - r.GET("/", func(c *Context) { - c.Header("X-Test", "original") - c.Header("X-Test-2", "present") - c.String(http.StatusOK, "hello world") - }) - c.Request = httptest.NewRequest("GET", "/", nil) - r.HandleContext(c) - // Result() has headers frozen when WriteHeaderNow() has been called - // Compared to this time, this is when the response headers will be flushed - // As response is flushed on c.String, the Header cannot be set by the first - // middleware. Assert this - assert.Equal(t, "", w.Result().Header.Get("X-Test")) - assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) -} - -func isGo117OrGo118() bool { - version := strings.Split(runtime.Version()[2:], ".") - if len(version) >= 2 { - x := version[0] - y := version[1] - if x == "1" && (y == "17" || y == "18") { - return true - } - } - return false -} diff --git a/context_1.16_test.go b/context_1.18_test.go similarity index 66% rename from context_1.16_test.go rename to context_1.18_test.go index 2676050..6118bea 100644 --- a/context_1.16_test.go +++ b/context_1.18_test.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build !go1.17 -// +build !go1.17 +//go:build !go1.19 package gin @@ -17,15 +16,22 @@ import ( "github.com/stretchr/testify/assert" ) -func TestContextFormFileFailed16(t *testing.T) { +func TestContextFormFileFailed18(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) - mw.Close() + defer func(mw *multipart.Writer) { + err := mw.Close() + if err != nil { + assert.Error(t, err) + } + }(mw) c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) c.engine.MaxMultipartMemory = 8 << 20 - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) + assert.Panics(t, func() { + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) + }) } diff --git a/context_1.19_test.go b/context_1.19_test.go index 4b34ea2..dd75325 100644 --- a/context_1.19_test.go +++ b/context_1.19_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go1.19 -// +build go1.19 package gin diff --git a/context_appengine.go b/context_appengine.go index 931313f..96b339c 100644 --- a/context_appengine.go +++ b/context_appengine.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build appengine -// +build appengine package gin diff --git a/context_test.go b/context_test.go index 1ab6b33..1dec902 100644 --- a/context_test.go +++ b/context_test.go @@ -37,7 +37,7 @@ var errTestRender = errors.New("TestRender") // Unit tests TODO // func (c *Context) File(filepath string) { // func (c *Context) Negotiate(code int, config Negotiate) { -// BAD case: func (c *Context) Render(code int, render render.Render, obj ...interface{}) { +// BAD case: func (c *Context) Render(code int, render render.Render, obj ...any) { // test that information is not leaked when reusing Contexts (using the Pool) func createMultipartRequest() *http.Request { @@ -2374,3 +2374,42 @@ func TestCreateTestContextWithRouteParams(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "hello gin", w.Body.String()) } + +type interceptedWriter struct { + ResponseWriter + b *bytes.Buffer +} + +func (i interceptedWriter) WriteHeader(code int) { + i.Header().Del("X-Test") + i.ResponseWriter.WriteHeader(code) +} + +func TestInterceptedHeader(t *testing.T) { + w := httptest.NewRecorder() + c, r := CreateTestContext(w) + + r.Use(func(c *Context) { + i := interceptedWriter{ + ResponseWriter: c.Writer, + b: bytes.NewBuffer(nil), + } + c.Writer = i + c.Next() + c.Header("X-Test", "overridden") + c.Writer = i.ResponseWriter + }) + r.GET("/", func(c *Context) { + c.Header("X-Test", "original") + c.Header("X-Test-2", "present") + c.String(http.StatusOK, "hello world") + }) + c.Request = httptest.NewRequest("GET", "/", nil) + r.HandleContext(c) + // Result() has headers frozen when WriteHeaderNow() has been called + // Compared to this time, this is when the response headers will be flushed + // As response is flushed on c.String, the Header cannot be set by the first + // middleware. Assert this + assert.Equal(t, "", w.Result().Header.Get("X-Test")) + assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) +} diff --git a/debug.go b/debug.go index cbcedbc..1fc0caf 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 16 +const ginSupportMinGoVer = 18 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.16+. + debugPrint(`[WARNING] Now Gin requires Go 1.18+. `) } diff --git a/debug_test.go b/debug_test.go index ce8b19d..2d5e9a5 100644 --- a/debug_test.go +++ b/debug_test.go @@ -21,7 +21,7 @@ import ( // TODO // func debugRoute(httpMethod, absolutePath string, handlers HandlersChain) { -// func debugPrint(format string, values ...interface{}) { +// func debugPrint(format string, values ...any) { func TestIsDebugging(t *testing.T) { SetMode(DebugMode) @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.16+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.18+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/deprecated.go b/deprecated.go index fdad855..9521308 100644 --- a/deprecated.go +++ b/deprecated.go @@ -13,7 +13,7 @@ import ( // BindWith binds the passed struct pointer using the specified binding engine. // See the binding package. func (c *Context) BindWith(obj any, b binding.Binding) error { - log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to + log.Println(`BindWith(\"any, binding.Binding\") error is going to be deprecated, please check issue #662 and either use MustBindWith() if you want HTTP 400 to be automatically returned if any error occur, or use ShouldBindWith() if you need to manage the error.`) diff --git a/docs/doc.md b/docs/doc.md index 7cebab5..e48c2ba 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -425,7 +425,7 @@ func main() { r.Use(gin.Logger()) // Recovery middleware recovers from any panics and writes a 500 if there was one. - r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { + r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) { if err, ok := recovered.(string); ok { c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err)) } @@ -996,7 +996,7 @@ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost: func main() { r := gin.Default() - // gin.H is a shortcut for map[string]interface{} + // gin.H is a shortcut for map[string]any r.GET("/someJSON", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) @@ -1961,7 +1961,7 @@ func (customerBinding) Name() string { return "form" } -func (customerBinding) Bind(req *http.Request, obj interface{}) error { +func (customerBinding) Bind(req *http.Request, obj any) error { if err := req.ParseForm(); err != nil { return err } @@ -1976,7 +1976,7 @@ func (customerBinding) Bind(req *http.Request, obj interface{}) error { return validate(obj) } -func validate(obj interface{}) error { +func validate(obj any) error { if binding.Validator == nil { return nil } diff --git a/internal/json/go_json.go b/internal/json/go_json.go index 23f7172..47c3559 100644 --- a/internal/json/go_json.go +++ b/internal/json/go_json.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go_json -// +build go_json package json diff --git a/internal/json/json.go b/internal/json/json.go index c5f3efc..c7ee83e 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -3,9 +3,6 @@ // license that can be found in the LICENSE file. //go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64) -// +build !jsoniter -// +build !go_json -// +build !sonic !avx !linux,!windows,!darwin !amd64 package json diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go index 853b1a9..45ed16b 100644 --- a/internal/json/jsoniter.go +++ b/internal/json/jsoniter.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build jsoniter -// +build jsoniter package json diff --git a/internal/json/sonic.go b/internal/json/sonic.go index 5a9ca4b..529e16d 100644 --- a/internal/json/sonic.go +++ b/internal/json/sonic.go @@ -3,10 +3,6 @@ // license that can be found in the LICENSE file. //go:build sonic && avx && (linux || windows || darwin) && amd64 -// +build sonic -// +build avx -// +build linux windows darwin -// +build amd64 package json diff --git a/render/any.go b/render/any.go deleted file mode 100644 index b19ad45..0000000 --- a/render/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package render - -type any = interface{} diff --git a/render/msgpack.go b/render/msgpack.go index e0f30f7..d1d8e84 100644 --- a/render/msgpack.go +++ b/render/msgpack.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package render diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index 6421236..db4b71e 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build !nomsgpack -// +build !nomsgpack package render diff --git a/testdata/protoexample/any.go b/testdata/protoexample/any.go deleted file mode 100644 index 2203f33..0000000 --- a/testdata/protoexample/any.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.18 -// +build !go1.18 - -package protoexample - -type any = interface{} diff --git a/utils.go b/utils.go index 4021a2a..47106a7 100644 --- a/utils.go +++ b/utils.go @@ -50,7 +50,7 @@ func WrapH(h http.Handler) HandlerFunc { } } -// H is a shortcut for map[string]interface{} +// H is a shortcut for map[string]any type H map[string]any // MarshalXML allows type H to be used with xml.Marshal. From d1b2408027e3dc61215e0591ef8735107848cbb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 10:04:56 +0800 Subject: [PATCH 776/912] chore(deps): bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#3515) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index da97874..d52e73c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.17 github.com/pelletier/go-toml/v2 v2.0.6 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 github.com/ugorji/go/codec v1.2.10 golang.org/x/net v0.7.0 google.golang.org/protobuf v1.28.1 diff --git a/go.sum b/go.sum index cab49ab..bb8225b 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= From 457fabd7e14f36ca1b5f302f7247efeb4690e49c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 10:05:28 +0800 Subject: [PATCH 777/912] chore(deps): bump github.com/bytedance/sonic from 1.8.1 to 1.8.2 (#3516) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d52e73c..484e388 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.18 require ( - github.com/bytedance/sonic v1.8.1 + github.com/bytedance/sonic v1.8.2 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.11.2 github.com/goccy/go-json v0.10.0 diff --git a/go.sum b/go.sum index bb8225b..6193e00 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.1 h1:NqAHCaGaTzro0xMmnTCLUyRlbEP6r8MCA1cJUrH3Pu4= -github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY= +github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= From de1c4ec54616e30ecf2a6645e596ad5aacaff2c9 Mon Sep 17 00:00:00 2001 From: lgbgbl <65756378+lgbgbl@users.noreply.github.com> Date: Wed, 1 Mar 2023 13:57:15 +0800 Subject: [PATCH 778/912] refactor: use bytes.ReplaceAll directly (#3455) --- recovery.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 2955c03..037be51 100644 --- a/recovery.go +++ b/recovery.go @@ -164,7 +164,7 @@ func function(pc uintptr) []byte { if period := bytes.Index(name, dot); period >= 0 { name = name[period+1:] } - name = bytes.Replace(name, centerDot, dot, -1) + name = bytes.ReplaceAll(name, centerDot, dot) return name } From a889c58de78711cb9b53de6cfcc9272c8518c729 Mon Sep 17 00:00:00 2001 From: hopehook Date: Thu, 2 Mar 2023 08:12:20 +0800 Subject: [PATCH 779/912] Convert strings and slices using the officially recommended way (#3344) * Feat: Convert strings and slices using the officially recommended way. Go official is expected to provide unsafe.{SliceData, Slice, StringData, String} series methods in version 1.20 for conversion of strings and slices. * chore: add reference documentation link to comment of code * chore: update Copyright * chore: remove build tag "+build !go1.20" --- go.mod | 2 +- .../{bytesconv.go => bytesconv_1.19.go} | 2 ++ internal/bytesconv/bytesconv_1.20.go | 23 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) rename internal/bytesconv/{bytesconv.go => bytesconv_1.19.go} (96%) create mode 100644 internal/bytesconv/bytesconv_1.20.go diff --git a/go.mod b/go.mod index 484e388..0358006 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gin-gonic/gin -go 1.18 +go 1.20 require ( github.com/bytedance/sonic v1.8.2 diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv_1.19.go similarity index 96% rename from internal/bytesconv/bytesconv.go rename to internal/bytesconv/bytesconv_1.19.go index 86e4c4d..669c9c9 100644 --- a/internal/bytesconv/bytesconv.go +++ b/internal/bytesconv/bytesconv_1.19.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. +//go:build !go1.20 + package bytesconv import ( diff --git a/internal/bytesconv/bytesconv_1.20.go b/internal/bytesconv/bytesconv_1.20.go new file mode 100644 index 0000000..5b6040a --- /dev/null +++ b/internal/bytesconv/bytesconv_1.20.go @@ -0,0 +1,23 @@ +// Copyright 2023 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go1.20 + +package bytesconv + +import ( + "unsafe" +) + +// StringToBytes converts string to byte slice without a memory allocation. +// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077. +func StringToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) +} + +// BytesToString converts byte slice to string without a memory allocation. +// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077. +func BytesToString(b []byte) string { + return unsafe.String(unsafe.SliceData(b), len(b)) +} From fe989b6a6f8091b2708b39a60b1dd2a2bd3b2812 Mon Sep 17 00:00:00 2001 From: Dylan Maassen van den Brink Date: Wed, 26 Apr 2023 05:18:22 +0200 Subject: [PATCH 780/912] docs: changed documentation link for trusted proxies (#3575) --- gin.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gin.go b/gin.go index f95e5dd..ed8b6da 100644 --- a/gin.go +++ b/gin.go @@ -515,7 +515,7 @@ func (engine *Engine) RunUnix(file string) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } listener, err := net.Listen("unix", file) @@ -538,7 +538,7 @@ func (engine *Engine) RunFd(fd int) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd)) @@ -559,7 +559,7 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } err = http.Serve(listener, engine.Handler()) From 757a638b7bbdd998375432fb22f693e82d7a7840 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 26 Apr 2023 14:13:56 +0800 Subject: [PATCH 781/912] chore: improve linting, testing, and GitHub Actions setup (#3583) - Update golangci-lint version from `v1.48.0` to `v1.52.2` - Remove Gitter notifications from GitHub Actions workflow - Add gosec linter settings and include specific rules - Exclude revive linter for test files - Remove Gitter badge from README.md - Delete codecov.yml file - Change function parameter name in fs.go - Remove unused parameter in defaultHandleRecovery function Signed-off-by: appleboy --- .github/workflows/gin.yml | 16 +--------------- .golangci.yml | 19 +++++++++++++++++++ README.md | 3 +-- codecov.yml | 5 ----- fs.go | 2 +- recovery.go | 2 +- 6 files changed, 23 insertions(+), 24 deletions(-) delete mode 100644 codecov.yml diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 5c1504a..b758c7f 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,7 +24,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.4.0 with: - version: v1.48.0 + version: v1.52.2 args: --verbose test: needs: lint @@ -75,17 +75,3 @@ jobs: - name: Format if: matrix.go-version == '1.20.x' run: diff -u <(echo -n) <(gofmt -d .) - notification-gitter: - needs: test - runs-on: ubuntu-latest - steps: - - name: Notification failure message - if: failure() - run: | - PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)" - curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" -d level=error https://webhooks.gitter.im/e/7f95bf605c4d356372f4 - - name: Notification success message - if: success() - run: | - PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)" - curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" https://webhooks.gitter.im/e/7f95bf605c4d356372f4 diff --git a/.golangci.yml b/.golangci.yml index c5e1de3..91dae02 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,22 @@ linters: - nolintlint - revive - wastedassign + +linters-settings: + gosec: + # To select a subset of rules to run. + # Available rules: https://github.com/securego/gosec#available-rules + # Default: [] - means include all rules + includes: + - G102 + - G106 + - G108 + - G109 + - G111 + - G112 + - G201 + - G203 + issues: exclude-rules: - linters: @@ -37,3 +53,6 @@ issues: - path: _test\.go linters: - gosec # security is not make sense in tests + - linters: + - revive + path: _test\.go diff --git a/README.md b/README.md index cba54ab..e007bf2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) -[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) @@ -176,4 +175,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor Gin is the work of hundreds of contributors. We appreciate your help! -Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. \ No newline at end of file +Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index c9c9a52..0000000 --- a/codecov.yml +++ /dev/null @@ -1,5 +0,0 @@ -coverage: - notify: - gitter: - default: - url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165 diff --git a/fs.go b/fs.go index 6427473..f17d743 100644 --- a/fs.go +++ b/fs.go @@ -39,7 +39,7 @@ func (fs onlyFilesFS) Open(name string) (http.File, error) { } // Readdir overrides the http.File default implementation. -func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) { +func (f neuteredReaddirFile) Readdir(_ int) ([]os.FileInfo, error) { // this disables directory listing return nil, nil } diff --git a/recovery.go b/recovery.go index 037be51..515f9d2 100644 --- a/recovery.go +++ b/recovery.go @@ -103,7 +103,7 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { } } -func defaultHandleRecovery(c *Context, err any) { +func defaultHandleRecovery(c *Context, _ any) { c.AbortWithStatus(http.StatusInternalServerError) } From eac2daac64811197970b5d2f6406e4ae6c31cb5e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 27 Apr 2023 10:16:59 +0800 Subject: [PATCH 782/912] chore: update dependencies for various packages and libraries (#3585) - Update bytedance/sonic to v1.8.8 - Update go-playground/validator/v10 to v10.12.0 - Update goccy/go-json to v0.10.2 - Update mattn/go-isatty to v0.0.18 - Update pelletier/go-toml/v2 to v2.0.7 - Update ugorji/go/codec to v1.2.11 - Update golang.org/x/net to v0.9.0 - Update google.golang.org/protobuf to v1.30.0 - Update klauspost/cpuid/v2 to v2.2.4 - Update leodido/go-urn to v1.2.3 - Update modern-go/concurrent to v0.0.0-20180306012644-bacd9c7ef1dd - Update golang.org/x/arch to v0.3.0 - Update golang.org/x/crypto to v0.8.0 - Update golang.org/x/sys to v0.7.0 - Update golang.org/x/text to v0.9.0 Signed-off-by: appleboy --- go.mod | 31 +++++++++++++------------- go.sum | 68 ++++++++++++++++++++++++++++------------------------------ 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index 0358006..5fa6200 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,17 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.8.2 + github.com/bytedance/sonic v1.8.8 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.11.2 - github.com/goccy/go-json v0.10.0 + github.com/go-playground/validator/v10 v10.12.0 + github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.17 - github.com/pelletier/go-toml/v2 v2.0.6 + github.com/mattn/go-isatty v0.0.18 + github.com/pelletier/go-toml/v2 v2.0.7 github.com/stretchr/testify v1.8.2 - github.com/ugorji/go/codec v1.2.10 - golang.org/x/net v0.7.0 - google.golang.org/protobuf v1.28.1 + github.com/ugorji/go/codec v1.2.11 + golang.org/x/net v0.9.0 + google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -22,15 +22,14 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.3 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect ) diff --git a/go.sum b/go.sum index 6193e00..90704f4 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,9 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY= -github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.8.8 h1:Kj4AYbZSeENfyXicsYppYKO0K2YWab+i2UTSY7Ukz9Q= +github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -15,39 +14,36 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= -github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= +github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= +github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= +github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -56,26 +52,28 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= -github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 6a0556ed5a67d1d12ae3e7ea2c0121b6c3b894e2 Mon Sep 17 00:00:00 2001 From: ccpro <92025731+CCpro10@users.noreply.github.com> Date: Wed, 10 May 2023 17:19:26 +0800 Subject: [PATCH 783/912] improve render code coverage (#3525) --- render/render_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/render/render_test.go b/render/render_test.go index 1925525..86dc362 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -15,6 +15,7 @@ import ( "strings" "testing" + "github.com/gin-gonic/gin/internal/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/proto" @@ -136,6 +137,51 @@ func TestRenderJsonpJSON(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } +type errorWriter struct { + bufString string + *httptest.ResponseRecorder +} + +var _ http.ResponseWriter = (*errorWriter)(nil) + +func (w *errorWriter) Write(buf []byte) (int, error) { + if string(buf) == w.bufString { + return 0, errors.New(`write "` + w.bufString + `" error`) + } + return w.ResponseRecorder.Write(buf) +} + +func TestRenderJsonpJSONError(t *testing.T) { + ew := &errorWriter{ + ResponseRecorder: httptest.NewRecorder(), + } + + jsonpJSON := JsonpJSON{ + Callback: "foo", + Data: map[string]string{ + "foo": "bar", + }, + } + + cb := template.JSEscapeString(jsonpJSON.Callback) + ew.bufString = cb + err := jsonpJSON.Render(ew) // error was returned while writing callback + assert.Equal(t, `write "`+cb+`" error`, err.Error()) + + ew.bufString = `(` + err = jsonpJSON.Render(ew) + assert.Equal(t, `write "`+`(`+`" error`, err.Error()) + + data, _ := json.Marshal(jsonpJSON.Data) // error was returned while writing data + ew.bufString = string(data) + err = jsonpJSON.Render(ew) + assert.Equal(t, `write "`+string(data)+`" error`, err.Error()) + + ew.bufString = `);` + err = jsonpJSON.Render(ew) + assert.Equal(t, `write "`+`);`+`" error`, err.Error()) +} + func TestRenderJsonpJSONError2(t *testing.T) { w := httptest.NewRecorder() data := map[string]any{ From 1ab268989db62a6dd86264cb20e14160e25a6a6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 16:45:22 +0800 Subject: [PATCH 784/912] chore(deps): bump golang.org/x/net from 0.9.0 to 0.10.0 (#3599) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.10.0. - [Commits](https://github.com/golang/net/compare/v0.9.0...v0.10.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5fa6200..3f3aa0e 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.7 github.com/stretchr/testify v1.8.2 github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.9.0 + golang.org/x/net v0.10.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,6 +30,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.8.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect ) diff --git a/go.sum b/go.sum index 90704f4..8450290 100644 --- a/go.sum +++ b/go.sum @@ -59,12 +59,12 @@ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 6bdc725c8dfdc203530f1c64c7ea1aaaf4aeaa40 Mon Sep 17 00:00:00 2001 From: Hiroki Nakano <33213547+hirokinakano@users.noreply.github.com> Date: Fri, 26 May 2023 12:45:46 +0900 Subject: [PATCH 785/912] Fix typos in ISSUE_TEMPLATE.md (#3616) --- .github/ISSUE_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6f8288d..864787c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -30,7 +30,7 @@ func main() { ``` -$ curl http://localhost:8201/hello/world +$ curl http://localhost:9000/hello/world Hello world ``` @@ -38,7 +38,7 @@ Hello world ``` -$ curl -i http://localhost:8201/hello/world +$ curl -i http://localhost:9000/hello/world ``` From 20cd6bcfc41148ae4acb01290496f818a61306aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 11:47:59 +0800 Subject: [PATCH 786/912] chore(deps): bump github.com/go-playground/validator/v10 (#3610) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.12.0 to 10.14.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.12.0...v10.14.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 5 +++-- go.sum | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 3f3aa0e..7ec3e4f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/bytedance/sonic v1.8.8 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.12.0 + github.com/go-playground/validator/v10 v10.14.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.18 @@ -20,10 +20,11 @@ require ( require ( github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect - github.com/leodido/go-urn v1.2.3 // indirect + github.com/leodido/go-urn v1.2.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 8450290..36d6b84 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -14,8 +16,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= -github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -27,8 +29,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= -github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= From 9f5ecd4be440f2789db917aa93c1b8afa2276917 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 11:50:31 +0800 Subject: [PATCH 787/912] chore(deps): bump actions/setup-go from 3 to 4 (#3543) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 4 ++-- .github/workflows/goreleaser.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index b758c7f..df6e194 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: '^1.18' - name: Checkout repository @@ -46,7 +46,7 @@ jobs: GOPROXY: https://proxy.golang.org steps: - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index baf02af..5b205ba 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: 1.20 - From 2d4bbec941551479b1fdf1e54ece03e6e82a7e72 Mon Sep 17 00:00:00 2001 From: Motoyasu Saburi Date: Mon, 29 May 2023 10:57:53 +0900 Subject: [PATCH 788/912] fix lack of escaping of filename in Content-Disposition (#3556) * fix lack of escaping of filename in Content-Disposition * add test for Content-Disposition filename escaping process * fix filename escape bypass problem fix backslashes before backquotes were not properly escaped problem. --- context.go | 8 +++++++- context_test.go | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 5716318..cb36087 100644 --- a/context.go +++ b/context.go @@ -1052,11 +1052,17 @@ func (c *Context) FileFromFS(filepath string, fs http.FileSystem) { http.FileServer(fs).ServeHTTP(c.Writer, c.Request) } +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +func escapeQuotes(s string) string { + return quoteEscaper.Replace(s) +} + // FileAttachment writes the specified file into the body stream in an efficient way // On the client side, the file will typically be downloaded with the given filename func (c *Context) FileAttachment(filepath, filename string) { if isASCII(filename) { - c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`) + c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+escapeQuotes(filename)+`"`) } else { c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename)) } diff --git a/context_test.go b/context_test.go index 1dec902..1805123 100644 --- a/context_test.go +++ b/context_test.go @@ -1032,6 +1032,20 @@ func TestContextRenderAttachment(t *testing.T) { assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition")) } +func TestContextRenderAndEscapeAttachment(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + maliciousFilename := "tampering_field.sh\"; \\\"; dummy=.go" + actualEscapedResponseFilename := "tampering_field.sh\\\"; \\\\\\\"; dummy=.go" + + c.Request, _ = http.NewRequest("GET", "/", nil) + c.FileAttachment("./gin.go", maliciousFilename) + + assert.Equal(t, 200, w.Code) + assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", actualEscapedResponseFilename), w.Header().Get("Content-Disposition")) +} + func TestContextRenderUTF8Attachment(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From bb1fc2e0fe97c63dab1527baab88d01183853b8f Mon Sep 17 00:00:00 2001 From: Bence Vidosits <38434845+bvidosits@users.noreply.github.com> Date: Mon, 29 May 2023 01:59:35 +0000 Subject: [PATCH 789/912] fix Request.Context() checks (#3512) Co-authored-by: Bence Vidosits --- context.go | 15 +++++++++++---- context_test.go | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index cb36087..420ff16 100644 --- a/context.go +++ b/context.go @@ -1180,9 +1180,16 @@ func (c *Context) SetAccepted(formats ...string) { /***** GOLANG.ORG/X/NET/CONTEXT *****/ /************************************/ +// hasRequestContext returns whether c.Request has Context and fallback. +func (c *Context) hasRequestContext() bool { + hasFallback := c.engine != nil && c.engine.ContextWithFallback + hasRequestContext := c.Request != nil && c.Request.Context() != nil + return hasFallback && hasRequestContext +} + // Deadline returns that there is no deadline (ok==false) when c.Request has no Context. func (c *Context) Deadline() (deadline time.Time, ok bool) { - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return } return c.Request.Context().Deadline() @@ -1190,7 +1197,7 @@ func (c *Context) Deadline() (deadline time.Time, ok bool) { // Done returns nil (chan which will wait forever) when c.Request has no Context. func (c *Context) Done() <-chan struct{} { - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return nil } return c.Request.Context().Done() @@ -1198,7 +1205,7 @@ func (c *Context) Done() <-chan struct{} { // Err returns nil when c.Request has no Context. func (c *Context) Err() error { - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return nil } return c.Request.Context().Err() @@ -1219,7 +1226,7 @@ func (c *Context) Value(key any) any { return val } } - if !c.engine.ContextWithFallback || c.Request == nil || c.Request.Context() == nil { + if !c.hasRequestContext() { return nil } return c.Request.Context().Value(key) diff --git a/context_test.go b/context_test.go index 1805123..70d4758 100644 --- a/context_test.go +++ b/context_test.go @@ -2176,6 +2176,24 @@ func TestRemoteIPFail(t *testing.T) { assert.False(t, trust) } +func TestHasRequestContext(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + assert.False(t, c.hasRequestContext(), "no request, no fallback") + c.engine.ContextWithFallback = true + assert.False(t, c.hasRequestContext(), "no request, has fallback") + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + assert.True(t, c.hasRequestContext(), "has request, has fallback") + c.Request, _ = http.NewRequestWithContext(nil, "", "", nil) //nolint:staticcheck + assert.False(t, c.hasRequestContext(), "has request with nil ctx, has fallback") + c.engine.ContextWithFallback = false + assert.False(t, c.hasRequestContext(), "has request, no fallback") + + c = &Context{} + assert.False(t, c.hasRequestContext(), "no request, no engine") + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) + assert.False(t, c.hasRequestContext(), "has request, no engine") +} + func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag From 4ea0e648e38a63d6caff14100f5eab5c50912bcd Mon Sep 17 00:00:00 2001 From: Adriano Sela Aviles Date: Wed, 31 May 2023 19:26:20 -0700 Subject: [PATCH 790/912] Ready release gin 1.9.1 (by: thinkerou) (#3630) * upgrade deps version * update change log * update version * update go mod * fix cr --------- Co-authored-by: thinkerou --- CHANGELOG.md | 21 +++++++++++++++++++++ go.mod | 10 +++++----- go.sum | 26 +++++++++++++++++--------- version.go | 2 +- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf24ec2..7968520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Gin ChangeLog +## Gin v1.9.1 + +### BUG FIXES + +* fix Request.Context() checks [#3512](https://github.com/gin-gonic/gin/pull/3512) + +### SECURITY + +* fix lack of escaping of filename in Content-Disposition [#3556](https://github.com/gin-gonic/gin/pull/3556) + +### ENHANCEMENTS + +* refactor: use bytes.ReplaceAll directly [#3455](https://github.com/gin-gonic/gin/pull/3455) +* convert strings and slices using the officially recommended way [#3344](https://github.com/gin-gonic/gin/pull/3344) +* improve render code coverage [#3525](https://github.com/gin-gonic/gin/pull/3525) + +### DOCS + +* docs: changed documentation link for trusted proxies [#3575](https://github.com/gin-gonic/gin/pull/3575) +* chore: improve linting, testing, and GitHub Actions setup [#3583](https://github.com/gin-gonic/gin/pull/3583) + ## Gin v1.9.0 ### BREAK CHANGES diff --git a/go.mod b/go.mod index 7ec3e4f..e37698e 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.8.8 + github.com/bytedance/sonic v1.9.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.14.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.18 - github.com/pelletier/go-toml/v2 v2.0.7 - github.com/stretchr/testify v1.8.2 + github.com/mattn/go-isatty v0.0.19 + github.com/pelletier/go-toml/v2 v2.0.8 + github.com/stretchr/testify v1.8.3 github.com/ugorji/go/codec v1.2.11 golang.org/x/net v0.10.0 google.golang.org/protobuf v1.30.0 @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.8.0 // indirect + golang.org/x/crypto v0.9.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect ) diff --git a/go.sum b/go.sum index 36d6b84..0a91a3e 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.8 h1:Kj4AYbZSeENfyXicsYppYKO0K2YWab+i2UTSY7Ukz9Q= -github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.9.0 h1:iwLYBds8bYtzwOX7kmdYwtS+aY2GgekVoIs2/IxR0tM= +github.com/bytedance/sonic v1.9.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= @@ -26,20 +28,22 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= -github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -50,17 +54,21 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/version.go b/version.go index 390da4f..85462e5 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.9.0" +const Version = "v1.9.1" From d4a64265f21993368c90602c18e778bf04ef36db Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 5 Jun 2023 09:52:39 +0800 Subject: [PATCH 791/912] chore(CI): update release args (#3595) --- .github/workflows/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 5b205ba..07a0548 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -29,6 +29,6 @@ jobs: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 02e754be9c4889f7ee56db0660cc611eb82b61d6 Mon Sep 17 00:00:00 2001 From: C <6714828+cpcf@users.noreply.github.com> Date: Fri, 4 Aug 2023 03:58:46 +0100 Subject: [PATCH 792/912] Upgrade golang.org/x/net -> v0.13.0 (#3684) Patches https://security.snyk.io/vuln/SNYK-GOLANG-GOLANGORGXNETHTML-5816820 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e37698e..ded1334 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 github.com/stretchr/testify v1.8.3 github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.10.0 + golang.org/x/net v0.13.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) From 62b50cfbc0de877207ff74c160a23dff6394f563 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 10 Aug 2023 17:06:34 +0800 Subject: [PATCH 793/912] chore: update dependencies to latest versions (#3694) - Update the version of `golang.org/x/crypto` from `v0.9.0` to `v0.11.0` - Update the version of `golang.org/x/sys` from `v0.8.0` to `v0.10.0` - Update the version of `golang.org/x/text` from `v0.9.0` to `v0.11.0` Signed-off-by: Bo-Yi Wu --- go.mod | 6 +++--- go.sum | 23 ++++++++--------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index ded1334..e129548 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.9.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect ) diff --git a/go.sum b/go.sum index 0a91a3e..147a110 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.0 h1:iwLYBds8bYtzwOX7kmdYwtS+aY2GgekVoIs2/IxR0tM= -github.com/bytedance/sonic v1.9.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= @@ -28,7 +26,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -36,7 +33,6 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -61,22 +57,19 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= +golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= From d16fdb15fa54ba898bf6f6ed757397282ed9e496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:23:47 +0800 Subject: [PATCH 794/912] chore(deps): bump golang.org/x/net from 0.13.0 to 0.14.0 (#3688) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index e129548..b133475 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 github.com/stretchr/testify v1.8.3 github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.13.0 + golang.org/x/net v0.14.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.11.0 // indirect - golang.org/x/sys v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect ) diff --git a/go.sum b/go.sum index 147a110..a2d587a 100644 --- a/go.sum +++ b/go.sum @@ -60,16 +60,16 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= From bb2d8cf486bde2dc69bf05ea917095260ac13723 Mon Sep 17 00:00:00 2001 From: Leonardo de Araujo <46436462+araujo88@users.noreply.github.com> Date: Sat, 12 Aug 2023 11:21:56 -0300 Subject: [PATCH 795/912] test(render): increased unit tests coverage (#3691) --- render/render_test.go | 13 +++++++++++++ response_writer_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/render/render_test.go b/render/render_test.go index 86dc362..c9db635 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -578,3 +578,16 @@ func TestRenderReaderNoContentLength(t *testing.T) { assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } + +func TestRenderWriteError(t *testing.T) { + data := []interface{}{"value1", "value2"} + prefix := "my-prefix:" + r := SecureJSON{Data: data, Prefix: prefix} + ew := &errorWriter{ + bufString: prefix, + ResponseRecorder: httptest.NewRecorder(), + } + err := r.Render(ew) + assert.NotNil(t, err) + assert.Equal(t, `write "my-prefix:" error`, err.Error()) +} diff --git a/response_writer_test.go b/response_writer_test.go index 9fd5e87..964aa30 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -156,3 +156,33 @@ func TestResponseWriterStatusCode(t *testing.T) { // status must be 200 although we tried to change it assert.Equal(t, http.StatusOK, w.Status()) } + +// mockPusherResponseWriter is an http.ResponseWriter that implements http.Pusher. +type mockPusherResponseWriter struct { + http.ResponseWriter +} + +func (m *mockPusherResponseWriter) Push(target string, opts *http.PushOptions) error { + return nil +} + +// nonPusherResponseWriter is an http.ResponseWriter that does not implement http.Pusher. +type nonPusherResponseWriter struct { + http.ResponseWriter +} + +func TestPusherWithPusher(t *testing.T) { + rw := &mockPusherResponseWriter{} + w := &responseWriter{ResponseWriter: rw} + + pusher := w.Pusher() + assert.NotNil(t, pusher, "Expected pusher to be non-nil") +} + +func TestPusherWithoutPusher(t *testing.T) { + rw := &nonPusherResponseWriter{} + w := &responseWriter{ResponseWriter: rw} + + pusher := w.Pusher() + assert.Nil(t, pusher, "Expected pusher to be nil") +} From e32b5e3a47c1aa238dc312fcddc45182a5b90032 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Aug 2023 16:58:10 +0800 Subject: [PATCH 796/912] chore(deps): bump golangci/golangci-lint-action from 3.4.0 to 3.7.0 (#3703) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index df6e194..54d76bb 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,7 +22,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.4.0 + uses: golangci/golangci-lint-action@v3.7.0 with: version: v1.52.2 args: --verbose From dc9cff732e27ce4ac21b25772a83c462a28b8b80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Aug 2023 16:58:36 +0800 Subject: [PATCH 797/912] chore(deps): bump github.com/go-playground/validator/v10 from 10.14.0 to 10.15.1 (#3702) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b133475..5c2ec05 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/bytedance/sonic v1.9.1 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.14.0 + github.com/go-playground/validator/v10 v10.15.1 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.19 diff --git a/go.sum b/go.sum index a2d587a..b992f59 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= +github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= From c2ba8f19ec19914b73290c53a32de479cd463555 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 22:18:00 +0800 Subject: [PATCH 798/912] chore(deps): bump actions/checkout from 3 to 4 (#3712) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- .github/workflows/gin.yml | 4 ++-- .github/workflows/goreleaser.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e27022d..b717a00 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 54d76bb..645616b 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -20,7 +20,7 @@ jobs: with: go-version: '^1.18' - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.7.0 with: @@ -51,7 +51,7 @@ jobs: go-version: ${{ matrix.go }} - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.ref }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 07a0548..4060926 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - From a481ee2897af1e368de5c919fbeb21b89aa26fc7 Mon Sep 17 00:00:00 2001 From: Viral Parmar Date: Wed, 27 Sep 2023 12:47:11 +0530 Subject: [PATCH 799/912] chore(http): use white color for HTTP 1XX (#3741) --- logger.go | 2 ++ logger_test.go | 1 + 2 files changed, 3 insertions(+) diff --git a/logger.go b/logger.go index cd1e7fa..1e6cf77 100644 --- a/logger.go +++ b/logger.go @@ -83,6 +83,8 @@ func (p *LogFormatterParams) StatusCodeColor() string { code := p.StatusCode switch { + case code >= http.StatusContinue && code < http.StatusOK: + return white case code >= http.StatusOK && code < http.StatusMultipleChoices: return green case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: diff --git a/logger_test.go b/logger_test.go index 5f78708..b93e1e0 100644 --- a/logger_test.go +++ b/logger_test.go @@ -310,6 +310,7 @@ func TestColorForStatus(t *testing.T) { return p.StatusCodeColor() } + assert.Equal(t, white, colorForStatus(http.StatusContinue), "1xx should be white") assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white") assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow") From bdde009dbbbae890db4e6ffdd252e2b4e63a1b85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 20:23:37 +0800 Subject: [PATCH 800/912] chore(deps): bump golang.org/x/net from 0.14.0 to 0.18.0 (#3774) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.14.0 to 0.18.0. - [Commits](https://github.com/golang/net/compare/v0.14.0...v0.18.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 5c2ec05..7ebadde 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/pelletier/go-toml/v2 v2.0.8 github.com/stretchr/testify v1.8.3 github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.14.0 + golang.org/x/net v0.18.0 google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/crypto v0.15.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index b992f59..75848b4 100644 --- a/go.sum +++ b/go.sum @@ -60,16 +60,16 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= From 0aeac86b05cd51c993304e7bcfc2e11cef025c83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 23:45:24 +0800 Subject: [PATCH 801/912] chore(deps): bump github.com/go-playground/validator/v10 from 10.15.1 to 10.16.0 (#3769) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.15.1 to 10.16.0. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.15.1...v10.16.0) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7ebadde..c365e77 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/bytedance/sonic v1.9.1 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.15.1 + github.com/go-playground/validator/v10 v10.16.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.19 diff --git a/go.sum b/go.sum index 75848b4..4bd36db 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= -github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= From 49f45a542719df661bd71dd48f1595f0bc1ff6f7 Mon Sep 17 00:00:00 2001 From: WeiTheShinobi <43955151+WeiTheShinobi@users.noreply.github.com> Date: Thu, 16 Nov 2023 23:46:11 +0800 Subject: [PATCH 802/912] docs: remove redundant comments (#3765) --- gin.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gin.go b/gin.go index ed8b6da..5a605cf 100644 --- a/gin.go +++ b/gin.go @@ -334,7 +334,6 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { } root.addRoute(path, handlers) - // Update maxParams if paramsCount := countParams(path); paramsCount > engine.maxParams { engine.maxParams = paramsCount } From 44d0dd70924dd154e3b98bc340accc53484efa9c Mon Sep 17 00:00:00 2001 From: Omkar P <45419097+omkar-foss@users.noreply.github.com> Date: Thu, 16 Nov 2023 21:16:43 +0530 Subject: [PATCH 803/912] fix: Add pointer support for url query params (#3659) (#3666) The pointer support in url query params (using []*Struct for binding query params) was previously available in Gin, but was removed in commit 0d50ce8 since there wasn't a test case for such a scenario, and so the case block was removed as a redundant one. --- binding/form_mapping.go | 5 +++++ binding/form_mapping_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 540bbbb..55435b9 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -239,6 +239,11 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) + case reflect.Ptr: + if !value.Elem().IsValid() { + value.Set(reflect.New(value.Type().Elem())) + } + return setWithProperType(val, value.Elem(), field) default: return errUnknownType } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 93d6a92..acea8f7 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -269,6 +269,39 @@ func TestMappingStructField(t *testing.T) { assert.Equal(t, 9, s.J.I) } +func TestMappingPtrField(t *testing.T) { + type ptrStruct struct { + Key int64 `json:"key"` + } + + type ptrRequest struct { + Items []*ptrStruct `json:"items" form:"items"` + } + + var err error + + // With 0 items. + var req0 ptrRequest + err = mappingByPtr(&req0, formSource{}, "form") + assert.NoError(t, err) + assert.Empty(t, req0.Items) + + // With 1 item. + var req1 ptrRequest + err = mappingByPtr(&req1, formSource{"items": {`{"key": 1}`}}, "form") + assert.NoError(t, err) + assert.Len(t, req1.Items, 1) + assert.EqualValues(t, 1, req1.Items[0].Key) + + // With 2 items. + var req2 ptrRequest + err = mappingByPtr(&req2, formSource{"items": {`{"key": 1}`, `{"key": 2}`}}, "form") + assert.NoError(t, err) + assert.Len(t, req2.Items, 2) + assert.EqualValues(t, 1, req2.Items[0].Key) + assert.EqualValues(t, 2, req2.Items[1].Key) +} + func TestMappingMapField(t *testing.T) { var s struct { M map[string]int From 386d244068db3693f938db4ead6d1f5f85942e3f Mon Sep 17 00:00:00 2001 From: Georgi Dimitrov <82881135+georgijd-form3@users.noreply.github.com> Date: Thu, 7 Dec 2023 00:38:55 +0000 Subject: [PATCH 804/912] fix(tree): correctly expand the capacity of params (#3502) --- routes_test.go | 39 +++++++++++++++++++++++++++++++++++++++ tree.go | 16 +++++++++++++++- tree_test.go | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/routes_test.go b/routes_test.go index 633c0ab..7a51f81 100644 --- a/routes_test.go +++ b/routes_test.go @@ -337,6 +337,45 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { assert.Equal(t, "/is/super/great", wild) } +// TestRouteParamsNotEmpty tests that context parameters will be set +// even if a route with params/wildcards is registered after the context +// initialisation (which happened in a previous requets). +func TestRouteParamsNotEmpty(t *testing.T) { + name := "" + lastName := "" + wild := "" + router := New() + + w := PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great") + + assert.Equal(t, http.StatusNotFound, w.Code) + + router.GET("/test/:name/:last_name/*wild", func(c *Context) { + name = c.Params.ByName("name") + lastName = c.Params.ByName("last_name") + var ok bool + wild, ok = c.Params.Get("wild") + + assert.True(t, ok) + assert.Equal(t, name, c.Param("name")) + assert.Equal(t, lastName, c.Param("last_name")) + + assert.Empty(t, c.Param("wtf")) + assert.Empty(t, c.Params.ByName("wtf")) + + wtf, ok := c.Params.Get("wtf") + assert.Empty(t, wtf) + assert.False(t, ok) + }) + + w = PerformRequest(router, http.MethodGet, "/test/john/smith/is/super/great") + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "john", name) + assert.Equal(t, "smith", lastName) + assert.Equal(t, "/is/super/great", wild) +} + // TestHandleStaticFile - ensure the static file handles properly func TestRouteStaticFile(t *testing.T) { // SETUP file diff --git a/tree.go b/tree.go index dda8f4f..7b1e008 100644 --- a/tree.go +++ b/tree.go @@ -497,7 +497,14 @@ walk: // Outer loop for walking the tree } // Save param value - if params != nil && cap(*params) > 0 { + if params != nil { + // Preallocate capacity if necessary + if cap(*params) < int(globalParamsCount) { + newParams := make(Params, len(*params), globalParamsCount) + copy(newParams, *params) + *params = newParams + } + if value.params == nil { value.params = params } @@ -544,6 +551,13 @@ walk: // Outer loop for walking the tree case catchAll: // Save param value if params != nil { + // Preallocate capacity if necessary + if cap(*params) < int(globalParamsCount) { + newParams := make(Params, len(*params), globalParamsCount) + copy(newParams, *params) + *params = newParams + } + if value.params == nil { value.params = params } diff --git a/tree_test.go b/tree_test.go index 2005738..aacc914 100644 --- a/tree_test.go +++ b/tree_test.go @@ -893,9 +893,9 @@ func TestTreeInvalidNodeType(t *testing.T) { func TestTreeInvalidParamsType(t *testing.T) { tree := &node{} - tree.wildChild = true - tree.children = append(tree.children, &node{}) - tree.children[0].nType = 2 + // add a child with wildcard + route := "/:path" + tree.addRoute(route, fakeHandler(route)) // set invalid Params type params := make(Params, 0) @@ -904,6 +904,34 @@ func TestTreeInvalidParamsType(t *testing.T) { tree.getValue("/test", ¶ms, getSkippedNodes(), false) } +func TestTreeExpandParamsCapacity(t *testing.T) { + data := []struct { + path string + }{ + {"/:path"}, + {"/*path"}, + } + + for _, item := range data { + tree := &node{} + tree.addRoute(item.path, fakeHandler(item.path)) + params := make(Params, 0) + + value := tree.getValue("/test", ¶ms, getSkippedNodes(), false) + + if value.params == nil { + t.Errorf("Expected %s params to be set, but they weren't", item.path) + continue + } + + if len(*value.params) != 1 { + t.Errorf("Wrong number of %s params: got %d, want %d", + item.path, len(*value.params), 1) + continue + } + } +} + func TestTreeWildcardConflictEx(t *testing.T) { conflicts := [...]struct { route string From 081b36ebdbf3635143dacd36b92e48529a98e34e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:27:25 +0800 Subject: [PATCH 805/912] chore(deps): bump actions/setup-go from 4 to 5 (#3798) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 4 ++-- .github/workflows/goreleaser.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 645616b..5f8c0c0 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '^1.18' - name: Checkout repository @@ -46,7 +46,7 @@ jobs: GOPROXY: https://proxy.golang.org steps: - name: Set up Go ${{ matrix.go }} - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 4060926..5364a91 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -19,7 +19,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: 1.20 - From 811f271a0491b3d74ce3c9948c10f95fe6f64206 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:27:57 +0800 Subject: [PATCH 806/912] chore(deps): bump goreleaser/goreleaser-action from 4 to 5 (#3721) Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 4 to 5. - [Release notes](https://github.com/goreleaser/goreleaser-action/releases) - [Commits](https://github.com/goreleaser/goreleaser-action/compare/v4...v5) --- updated-dependencies: - dependency-name: goreleaser/goreleaser-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 5364a91..0180323 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -24,7 +24,7 @@ jobs: go-version: 1.20 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v5 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser From 53fbf4dbfbf465b552057e6f8d199a275151b7a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:28:51 +0800 Subject: [PATCH 807/912] chore(deps): bump github.com/pelletier/go-toml/v2 from 2.0.8 to 2.1.1 (#3797) Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.8 to 2.1.1. - [Release notes](https://github.com/pelletier/go-toml/releases) - [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml) - [Commits](https://github.com/pelletier/go-toml/compare/v2.0.8...v2.1.1) --- updated-dependencies: - dependency-name: github.com/pelletier/go-toml/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c365e77..6b348fe 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.19 - github.com/pelletier/go-toml/v2 v2.0.8 - github.com/stretchr/testify v1.8.3 + github.com/pelletier/go-toml/v2 v2.1.1 + github.com/stretchr/testify v1.8.4 github.com/ugorji/go/codec v1.2.11 golang.org/x/net v0.18.0 google.golang.org/protobuf v1.30.0 diff --git a/go.sum b/go.sum index 4bd36db..39d74fc 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -51,8 +51,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= From 160c1730efd30046239c802d5b9f895a708c3f4c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 19 Jan 2024 00:35:08 +0800 Subject: [PATCH 808/912] chore: update GitHub Actions configuration (#3792) - Change the cron schedule from `'0 17 * * 5'` to `"0 17 * * 5"` in the file `.github/workflows/codeql.yml` - Change the value of `language` from `['go']` to `["go"]` in the file `.github/workflows/codeql.yml` - Change the value of `go-version` from `'^1.18'` to `"^1.18"` in the file `.github/workflows/gin.yml` - Add `1.21` to the list of `go` versions and change the value of `test-tags` in the file `.github/workflows/gin.yml` - Change the value of `if` condition from `matrix.go-version == '1.20.x'` to `matrix.go-version == '1.21.x'` in the file `.github/workflows/gin.yml` - Change the value of `on` from `'*'` to `"*"` in the file `.github/workflows/goreleaser.yml` - Change the name of the job from `name: Checkout` to `name: Checkout` in the file `.github/workflows/goreleaser.yml` - Change the name of the job from `name: Set up Go` to `name: Set up Go` in the file `.github/workflows/goreleaser.yml` - Change the value of `go-version` from `1.20` to `"^1"` in Signed-off-by: Bo-Yi Wu --- .github/workflows/codeql.yml | 8 ++++---- .github/workflows/gin.yml | 8 ++++---- .github/workflows/goreleaser.yml | 7 +++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b717a00..858124e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -7,12 +7,12 @@ name: "CodeQL" on: push: - branches: [ master ] + branches: [master] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [master] schedule: - - cron: '0 17 * * 5' + - cron: "0 17 * * 5" jobs: analyze: @@ -29,7 +29,7 @@ jobs: # Override automatic language detection by changing the below list # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] # TODO: Enable for javascript later - language: [ 'go'] + language: ["go"] steps: - name: Checkout repository diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 5f8c0c0..2149a21 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -18,7 +18,7 @@ jobs: - name: Setup go uses: actions/setup-go@v5 with: - go-version: '^1.18' + go-version: "^1.18" - name: Checkout repository uses: actions/checkout@v4 - name: Setup golangci-lint @@ -31,8 +31,8 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ['1.18', '1.19', '1.20'] - test-tags: ['', '-tags nomsgpack', '-tags "sonic avx"', '-tags go_json'] + go: ["1.18", "1.19", "1.20", "1.21"] + test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json"] include: - os: ubuntu-latest go-build: ~/.cache/go-build @@ -73,5 +73,5 @@ jobs: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - name: Format - if: matrix.go-version == '1.20.x' + if: matrix.go-version == '1.21.x' run: diff -u <(echo -n) <(gofmt -d .) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 0180323..cbd5d41 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -3,7 +3,7 @@ name: Goreleaser on: push: tags: - - '*' + - "*" permissions: contents: write @@ -12,8 +12,7 @@ jobs: goreleaser: runs-on: ubuntu-latest steps: - - - name: Checkout + - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 @@ -21,7 +20,7 @@ jobs: name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.20 + go-version: "^1" - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 From 857db39f82fb82456af2906ccea972ae1d65ff57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 08:18:57 +0800 Subject: [PATCH 809/912] chore(deps): bump github/codeql-action from 2 to 3 (#3806) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 858124e..9a4c40d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -46,4 +46,4 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 4a40f8f1a49b9086b461d97e167c3b9628d8b923 Mon Sep 17 00:00:00 2001 From: caption <101684156+chncaption@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:00:17 +0800 Subject: [PATCH 810/912] fix(sec): upgrade golang.org/x/crypto to 0.17.0 (#3832) --- go.mod | 4 ++-- go.sum | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 6b348fe..7f56045 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.15.0 // indirect - golang.org/x/sys v0.14.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 39d74fc..f4bf8ff 100644 --- a/go.sum +++ b/go.sum @@ -62,12 +62,15 @@ golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 8ab47c694ea93fdb442b617961ce9b3171151749 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 1 Feb 2024 11:03:26 +0800 Subject: [PATCH 811/912] ci(lint): update tooling and workflows for consistency (#3834) * chore: update tooling and workflows for consistency - Update the version of a tool in the GitHub workflow from `v1.52.2` to `v1.55.2` Signed-off-by: Bo-Yi Wu * chore: refactor linter configuration in CI - Remove the `depguard` linter from the `.golangci.yml` configuration Signed-off-by: Bo-Yi Wu * ci: refine CI workflow and test configurations - Disable caching in the GitHub Actions workflow for `gin.yml` Signed-off-by: Bo-Yi Wu * refactor: refactor return logic in tree operations - Modify multiple return statements in `tree.go` to return a specific value instead of nothing Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 3 ++- .golangci.yml | 1 - go.sum | 5 +---- tree.go | 22 +++++++++++----------- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 2149a21..4ec9543 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,7 +24,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.7.0 with: - version: v1.52.2 + version: v1.55.2 args: --verbose test: needs: lint @@ -49,6 +49,7 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} + cache: false - name: Checkout Code uses: actions/checkout@v4 diff --git a/.golangci.yml b/.golangci.yml index 91dae02..4a72f73 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,7 +3,6 @@ run: linters: enable: - asciicheck - - depguard - dogsled - durationcheck - errcheck diff --git a/go.sum b/go.sum index f4bf8ff..8c4ef3f 100644 --- a/go.sum +++ b/go.sum @@ -60,16 +60,13 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/tree.go b/tree.go index 7b1e008..4564646 100644 --- a/tree.go +++ b/tree.go @@ -478,7 +478,7 @@ walk: // Outer loop for walking the tree // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. value.tsr = path == "/" && n.handlers != nil - return + return value } // Handle wildcard child, which is always at the end of the array @@ -533,12 +533,12 @@ walk: // Outer loop for walking the tree // ... but we can't value.tsr = len(path) == end+1 - return + return value } if value.handlers = n.handlers; value.handlers != nil { value.fullPath = n.fullPath - return + return value } if len(n.children) == 1 { // No handle found. Check if a handle for this path + a @@ -546,7 +546,7 @@ walk: // Outer loop for walking the tree n = n.children[0] value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/") } - return + return value case catchAll: // Save param value @@ -578,7 +578,7 @@ walk: // Outer loop for walking the tree value.handlers = n.handlers value.fullPath = n.fullPath - return + return value default: panic("invalid node type") @@ -609,7 +609,7 @@ walk: // Outer loop for walking the tree // Check if this node has a handle registered. if value.handlers = n.handlers; value.handlers != nil { value.fullPath = n.fullPath - return + return value } // If there is no handle for this route, but this route has a @@ -617,12 +617,12 @@ walk: // Outer loop for walking the tree // additional trailing slash if path == "/" && n.wildChild && n.nType != root { value.tsr = true - return + return value } if path == "/" && n.nType == static { value.tsr = true - return + return value } // No handle found. Check if a handle for this path + a @@ -632,11 +632,11 @@ walk: // Outer loop for walking the tree n = n.children[i] value.tsr = (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) - return + return value } } - return + return value } // Nothing found. We can recommend to redirect to the same URL with an @@ -662,7 +662,7 @@ walk: // Outer loop for walking the tree } } - return + return value } } From a64286a7760be2031209686ce4d36e99d42dd419 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 1 Feb 2024 12:17:36 +0800 Subject: [PATCH 812/912] chore(deps): update dependencies to latest versions (#3835) * chore: update dependencies to latest versions - Update `sonic` library from `v1.9.1` to `v1.10.2` - Update `validator` library from `v10.16.0` to `v10.17.0` - Update `go-isatty` library from `v0.0.19` to `v0.0.20` - Update `go/codec`, `x/net`, and `protobuf` libraries to newer versions - Update `base64x` to a newer commit and add `iasm` library as an indirect dependency - Update `mimetype`, `cpuid`, `go-urn`, `x/arch`, `x/crypto`, and `x/sys` libraries to newer versions Signed-off-by: Bo-Yi Wu * ci: refactor CI workflows and improve robustness - Update GitHub Actions cache from v3 to v4 in the workflow configuration Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 2 +- go.mod | 27 +++++++++-------- go.sum | 64 ++++++++++++++++++++------------------- 3 files changed, 48 insertions(+), 45 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 4ec9543..b36e101 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -56,7 +56,7 @@ jobs: with: ref: ${{ github.ref }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: | ${{ matrix.go-build }} diff --git a/go.mod b/go.mod index 7f56045..0b60c5d 100644 --- a/go.mod +++ b/go.mod @@ -3,34 +3,35 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.9.1 + github.com/bytedance/sonic v1.10.2 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.16.0 + github.com/go-playground/validator/v10 v10.17.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 - github.com/mattn/go-isatty v0.0.19 + github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.1.1 github.com/stretchr/testify v1.8.4 - github.com/ugorji/go/codec v1.2.11 - golang.org/x/net v0.18.0 - google.golang.org/protobuf v1.30.0 + github.com/ugorji/go/codec v1.2.12 + golang.org/x/net v0.20.0 + google.golang.org/protobuf v1.32.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/leodido/go-urn v1.3.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 8c4ef3f..e360d9d 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,19 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= +github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -16,23 +21,22 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= +github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.3.0 h1:jX8FDLfW4ThVXctBNZ+3cIWnCSnrACDV73r76dy0aQQ= +github.com/leodido/go-urn v1.3.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -50,34 +54,32 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From c6ae2e69666a2b36203b29650ee75d172c725c66 Mon Sep 17 00:00:00 2001 From: Ghobad Date: Fri, 2 Feb 2024 05:22:26 +0330 Subject: [PATCH 813/912] feat(logger): ability to skip logs based on user-defined logic (#3593) * log skipper * do not call time.now() if logging should be skipped * do not ignore skip func delay in latency calculation * write docs * write test --- docs/doc.md | 38 +++++++++++++++++++++++++++++++++++++ logger.go | 51 +++++++++++++++++++++++++++++--------------------- logger_test.go | 20 ++++++++++++++++++++ 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/docs/doc.md b/docs/doc.md index e48c2ba..520d105 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -508,6 +508,44 @@ Sample Output ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " ``` +### Skip logging + +```go +func main() { + router := gin.New() + + // skip logging for desired paths by setting SkipPaths in LoggerConfig + loggerConfig := gin.LoggerConfig{SkipPaths: []string{"/metrics"}} + + // skip logging based on your logic by setting Skip func in LoggerConfig + loggerConfig.Skip = func(c *gin.Context) bool { + // as an example skip non server side errors + return c.Writer.Status() < http.StatusInternalServerError + } + + engine.Use(gin.LoggerWithConfig(loggerConfig)) + router.Use(gin.Recovery()) + + // skipped + router.GET("/metrics", func(c *gin.Context) { + c.Status(http.StatusNotImplemented) + }) + + // skipped + router.GET("/ping", func(c *gin.Context) { + c.String(http.StatusOK, "pong") + }) + + // not skipped + router.GET("/data", func(c *gin.Context) { + c.Status(http.StatusNotImplemented) + }) + + router.Run(":8080") +} + +``` + ### Controlling Log output coloring By default, logs output on console should be colorized depending on the detected TTY. diff --git a/logger.go b/logger.go index 1e6cf77..db2c683 100644 --- a/logger.go +++ b/logger.go @@ -47,8 +47,15 @@ type LoggerConfig struct { // SkipPaths is an url path array which logs are not written. // Optional. SkipPaths []string + + // Skip is a Skipper that indicates which logs should not be written. + // Optional. + Skip Skipper } +// Skipper is a function to skip logs based on provided Context +type Skipper func(c *Context) bool + // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter type LogFormatter func(params LogFormatterParams) string @@ -241,32 +248,34 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { // Process request c.Next() - // Log only when path is not being skipped - if _, ok := skip[path]; !ok { - param := LogFormatterParams{ - Request: c.Request, - isTerm: isTerm, - Keys: c.Keys, - } - - // Stop timer - param.TimeStamp = time.Now() - param.Latency = param.TimeStamp.Sub(start) + // Log only when it is not being skipped + if _, ok := skip[path]; ok || (conf.Skip != nil && conf.Skip(c)) { + return + } - param.ClientIP = c.ClientIP() - param.Method = c.Request.Method - param.StatusCode = c.Writer.Status() - param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() + param := LogFormatterParams{ + Request: c.Request, + isTerm: isTerm, + Keys: c.Keys, + } - param.BodySize = c.Writer.Size() + // Stop timer + param.TimeStamp = time.Now() + param.Latency = param.TimeStamp.Sub(start) - if raw != "" { - path = path + "?" + raw - } + param.ClientIP = c.ClientIP() + param.Method = c.Request.Method + param.StatusCode = c.Writer.Status() + param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() - param.Path = path + param.BodySize = c.Writer.Size() - fmt.Fprint(out, formatter(param)) + if raw != "" { + path = path + "?" + raw } + + param.Path = path + + fmt.Fprint(out, formatter(param)) } } diff --git a/logger_test.go b/logger_test.go index b93e1e0..6c1814d 100644 --- a/logger_test.go +++ b/logger_test.go @@ -415,6 +415,26 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { assert.Contains(t, buffer.String(), "") } +func TestLoggerWithConfigSkipper(t *testing.T) { + buffer := new(strings.Builder) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + Skip: func(c *Context) bool { + return c.Writer.Status() == http.StatusNoContent + }, + })) + router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) }) + router.GET("/skipped", func(c *Context) { c.Status(http.StatusNoContent) }) + + PerformRequest(router, "GET", "/logged") + assert.Contains(t, buffer.String(), "200") + + buffer.Reset() + PerformRequest(router, "GET", "/skipped") + assert.Contains(t, buffer.String(), "") +} + func TestDisableConsoleColor(t *testing.T) { New() assert.Equal(t, autoColor, consoleColorMode) From 9f598a31aafb92d675f38f1c8371e4ac76f858bf Mon Sep 17 00:00:00 2001 From: Prakhar Gurunani Date: Sun, 4 Feb 2024 18:44:29 +0530 Subject: [PATCH 814/912] fix(router): catch-all conflicting wildcard (#3812) * fix: catch-all conflicting wildcard * add: test cases * chore: update GitHub Actions configuration (#3792) - Change the cron schedule from `'0 17 * * 5'` to `"0 17 * * 5"` in the file `.github/workflows/codeql.yml` - Change the value of `language` from `['go']` to `["go"]` in the file `.github/workflows/codeql.yml` - Change the value of `go-version` from `'^1.18'` to `"^1.18"` in the file `.github/workflows/gin.yml` - Add `1.21` to the list of `go` versions and change the value of `test-tags` in the file `.github/workflows/gin.yml` - Change the value of `if` condition from `matrix.go-version == '1.20.x'` to `matrix.go-version == '1.21.x'` in the file `.github/workflows/gin.yml` - Change the value of `on` from `'*'` to `"*"` in the file `.github/workflows/goreleaser.yml` - Change the name of the job from `name: Checkout` to `name: Checkout` in the file `.github/workflows/goreleaser.yml` - Change the name of the job from `name: Set up Go` to `name: Set up Go` in the file `.github/workflows/goreleaser.yml` - Change the value of `go-version` from `1.20` to `"^1"` in Signed-off-by: Bo-Yi Wu * chore(deps): bump github/codeql-action from 2 to 3 (#3806) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(sec): upgrade golang.org/x/crypto to 0.17.0 (#3832) * ci(lint): update tooling and workflows for consistency (#3834) * chore: update tooling and workflows for consistency - Update the version of a tool in the GitHub workflow from `v1.52.2` to `v1.55.2` Signed-off-by: Bo-Yi Wu * chore: refactor linter configuration in CI - Remove the `depguard` linter from the `.golangci.yml` configuration Signed-off-by: Bo-Yi Wu * ci: refine CI workflow and test configurations - Disable caching in the GitHub Actions workflow for `gin.yml` Signed-off-by: Bo-Yi Wu * refactor: refactor return logic in tree operations - Modify multiple return statements in `tree.go` to return a specific value instead of nothing Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu * chore(deps): update dependencies to latest versions (#3835) * chore: update dependencies to latest versions - Update `sonic` library from `v1.9.1` to `v1.10.2` - Update `validator` library from `v10.16.0` to `v10.17.0` - Update `go-isatty` library from `v0.0.19` to `v0.0.20` - Update `go/codec`, `x/net`, and `protobuf` libraries to newer versions - Update `base64x` to a newer commit and add `iasm` library as an indirect dependency - Update `mimetype`, `cpuid`, `go-urn`, `x/arch`, `x/crypto`, and `x/sys` libraries to newer versions Signed-off-by: Bo-Yi Wu * ci: refactor CI workflows and improve robustness - Update GitHub Actions cache from v3 to v4 in the workflow configuration Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu * wip: fix tests * wip: fix tests --------- Signed-off-by: Bo-Yi Wu Signed-off-by: dependabot[bot] Co-authored-by: Bo-Yi Wu Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: caption <101684156+chncaption@users.noreply.github.com> --- tree.go | 5 ++++- tree_test.go | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tree.go b/tree.go index 4564646..878023d 100644 --- a/tree.go +++ b/tree.go @@ -351,7 +351,10 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) } if len(n.path) > 0 && n.path[len(n.path)-1] == '/' { - pathSeg := strings.SplitN(n.children[0].path, "/", 2)[0] + pathSeg := "" + if len(n.children) != 0 { + pathSeg = strings.SplitN(n.children[0].path, "/", 2)[0] + } panic("catch-all wildcard '" + path + "' in new path '" + fullPath + "' conflicts with existing path segment '" + pathSeg + diff --git a/tree_test.go b/tree_test.go index aacc914..c9b0313 100644 --- a/tree_test.go +++ b/tree_test.go @@ -417,6 +417,8 @@ func TestTreeWildcardConflict(t *testing.T) { {"/user_:name", false}, {"/id:id", false}, {"/id/:id", false}, + {"/static/*file", false}, + {"/static/", true}, } testRoutes(t, routes) } From 3dc1cd6572b4e3a0cd170a15debe546c2c72294f Mon Sep 17 00:00:00 2001 From: clearcode <34591322+clearcodecn@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:46:35 +0800 Subject: [PATCH 815/912] fix(binding): binding error while not upload file (#3819) (#3820) Co-authored-by: zhangmj --- binding/form_mapping.go | 3 +++ binding/form_mapping_test.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 55435b9..77a1bde 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,6 +7,7 @@ package binding import ( "errors" "fmt" + "mime/multipart" "reflect" "strconv" "strings" @@ -235,6 +236,8 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel switch value.Interface().(type) { case time.Time: return setTimeField(val, field, value) + case multipart.FileHeader: + return nil } return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface()) case reflect.Map: diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index acea8f7..16527eb 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -5,6 +5,7 @@ package binding import ( + "mime/multipart" "reflect" "testing" "time" @@ -43,6 +44,7 @@ func TestMappingBaseTypes(t *testing.T) { {"zero value", struct{ F uint }{}, "", uint(0)}, {"zero value", struct{ F bool }{}, "", false}, {"zero value", struct{ F float32 }{}, "", float32(0)}, + {"file value", struct{ F *multipart.FileHeader }{}, "", &multipart.FileHeader{}}, } { tp := reflect.TypeOf(tt.value) testName := tt.name + ":" + tp.Field(0).Type.String() From e957d1abf13846e458956d8c97e7b7c76c7ee9a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 10:39:24 +0800 Subject: [PATCH 816/912] chore(deps): bump codecov/codecov-action from 3 to 4 (#3838) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index b36e101..75e6d05 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -69,7 +69,7 @@ jobs: run: make test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} From 86ff4a64c7efe1a1c875529835eeef9e15de1e86 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Tue, 6 Feb 2024 04:08:56 +0100 Subject: [PATCH 817/912] fix(header): Allow header according to RFC 7231 (HTTP 405) (#3759) Co-authored-by: Helios --- gin.go | 14 +++++++++++--- routes_test.go | 12 ++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/gin.go b/gin.go index 5a605cf..b6ac535 100644 --- a/gin.go +++ b/gin.go @@ -633,17 +633,25 @@ func (engine *Engine) handleHTTPRequest(c *Context) { } if engine.HandleMethodNotAllowed { + // According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response + // containing a list of the target resource's currently supported methods. + allowed := make([]string, 0, len(t)-1) for _, tree := range engine.trees { if tree.method == httpMethod { continue } if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil { - c.handlers = engine.allNoMethod - serveError(c, http.StatusMethodNotAllowed, default405Body) - return + allowed = append(allowed, tree.method) } } + if len(allowed) > 0 { + c.handlers = engine.allNoMethod + c.writermem.Header().Set("Allow", strings.Join(allowed, ", ")) + serveError(c, http.StatusMethodNotAllowed, default405Body) + return + } } + c.handlers = engine.allNoRoute serveError(c, http.StatusNotFound, default404Body) } diff --git a/routes_test.go b/routes_test.go index 7a51f81..a0ff695 100644 --- a/routes_test.go +++ b/routes_test.go @@ -514,6 +514,18 @@ func TestRouteNotAllowedEnabled2(t *testing.T) { assert.Equal(t, http.StatusMethodNotAllowed, w.Code) } +func TestRouteNotAllowedEnabled3(t *testing.T) { + router := New() + router.HandleMethodNotAllowed = true + router.GET("/path", func(c *Context) {}) + router.POST("/path", func(c *Context) {}) + w := PerformRequest(router, http.MethodPut, "/path") + assert.Equal(t, http.StatusMethodNotAllowed, w.Code) + allowed := w.Header().Get("Allow") + assert.Contains(t, allowed, "GET") + assert.Contains(t, allowed, "POST") +} + func TestRouteNotAllowedDisabled(t *testing.T) { router := New() router.HandleMethodNotAllowed = false From 82bcd6d39bfe9c22032764ff3b0b6f8ef1673e49 Mon Sep 17 00:00:00 2001 From: Alonso Villegas Date: Wed, 7 Feb 2024 06:44:11 -0500 Subject: [PATCH 818/912] fix(binding): dereference pointer to struct (#3199) --- binding/default_validator.go | 5 ++++- binding/validate_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index e216b85..ac43d7c 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -54,7 +54,10 @@ func (v *defaultValidator) ValidateStruct(obj any) error { value := reflect.ValueOf(obj) switch value.Kind() { case reflect.Ptr: - return v.ValidateStruct(value.Elem().Interface()) + if value.Elem().Kind() != reflect.Struct { + return v.ValidateStruct(value.Elem().Interface()) + } + return v.validateStruct(obj) case reflect.Struct: return v.validateStruct(obj) case reflect.Slice, reflect.Array: diff --git a/binding/validate_test.go b/binding/validate_test.go index 801bd9b..1fc15ff 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -192,6 +192,30 @@ func TestValidatePrimitives(t *testing.T) { assert.Equal(t, "value", str) } +type structModifyValidation struct { + Integer int +} + +func toZero(sl validator.StructLevel) { + var s *structModifyValidation = sl.Top().Interface().(*structModifyValidation) + s.Integer = 0 +} + +func TestValidateAndModifyStruct(t *testing.T) { + // This validates that pointers to structs are passed to the validator + // giving us the ability to modify the struct being validated. + engine, ok := Validator.Engine().(*validator.Validate) + assert.True(t, ok) + + engine.RegisterStructValidation(toZero, structModifyValidation{}) + + s := structModifyValidation{Integer: 1} + errs := validate(&s) + + assert.Nil(t, errs) + assert.Equal(t, s, structModifyValidation{Integer: 0}) +} + // structCustomValidation is a helper struct we use to check that // custom validation can be registered on it. // The `notone` binding directive is for custom validation and registered later. From bb3519d26f52835cf00e5e430b52651a9c378c97 Mon Sep 17 00:00:00 2001 From: Andy Brody Date: Wed, 7 Feb 2024 07:18:53 -0500 Subject: [PATCH 819/912] chore(IP): add TrustedPlatform constant for Fly.io. (#3839) Also add some more detail to the docs for how to use TrustedPlatform. https://fly.io/docs/reference/runtime-environment/#fly-client-ip --- context_test.go | 7 +++++++ docs/doc.md | 12 +++++++++--- gin.go | 2 ++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/context_test.go b/context_test.go index 70d4758..88165c0 100644 --- a/context_test.go +++ b/context_test.go @@ -1569,6 +1569,12 @@ func TestContextClientIP(t *testing.T) { c.Request.Header.Del("CF-Connecting-IP") assert.Equal(t, "40.40.40.40", c.ClientIP()) + c.engine.TrustedPlatform = PlatformFlyIO + assert.Equal(t, "70.70.70.70", c.ClientIP()) + + c.Request.Header.Del("Fly-Client-IP") + assert.Equal(t, "40.40.40.40", c.ClientIP()) + c.engine.TrustedPlatform = "" // no port @@ -1581,6 +1587,7 @@ func resetContextForClientIPTests(c *Context) { c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30") c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50") c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60") + c.Request.Header.Set("Fly-Client-IP", "70.70.70.70") c.Request.RemoteAddr = " 40.40.40.40:42123 " c.engine.TrustedPlatform = "" c.engine.trustedCIDRs = defaultTrustedCIDRs diff --git a/docs/doc.md b/docs/doc.md index 520d105..df006e8 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -2214,10 +2214,16 @@ import ( func main() { router := gin.Default() // Use predefined header gin.PlatformXXX + // Google App Engine router.TrustedPlatform = gin.PlatformGoogleAppEngine - // Or set your own trusted request header for another trusted proxy service - // Don't set it to any suspect request header, it's unsafe - router.TrustedPlatform = "X-CDN-IP" + // Cloudflare + router.TrustedPlatform = gin.PlatformCloudflare + // Fly.io + router.TrustedPlatform = gin.PlatformFlyIO + // Or, you can set your own trusted request header. But be sure your CDN + // prevents users from passing this header! For example, if your CDN puts + // the client IP in X-CDN-Client-IP: + router.TrustedPlatform = "X-CDN-Client-IP" router.GET("/", func(c *gin.Context) { // If you set TrustedPlatform, ClientIP() will resolve the diff --git a/gin.go b/gin.go index b6ac535..24a9864 100644 --- a/gin.go +++ b/gin.go @@ -77,6 +77,8 @@ const ( // PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining // the client's IP PlatformCloudflare = "CF-Connecting-IP" + // PlatformFlyIO when running on Fly.io. Trust Fly-Client-IP for determining the client's IP + PlatformFlyIO = "Fly-Client-IP" ) // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. From 000fdb3ac95c7c318440afbd98eaf60f7430a1db Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 18 Feb 2024 17:32:06 +0800 Subject: [PATCH 820/912] ci(testing): add go1.22 version (#3842) * chore: update gin version and remove unnecessary steps in GitHub workflows - Update the version of gin to v1.56.1 in the `.github/workflows/gin.yml` file - Add go version 1.22 to the list of supported versions in the `.github/workflows/gin.yml` file - Remove the unnecessary step "Set up Go" in the `.github/workflows/goreleaser.yml` file - Update the step name "Run GoReleaser" in the `.github/workflows/goreleaser.yml` file Signed-off-by: appleboy * ci: update dependencies and CI configurations - Update conditional Go version check in GitHub Actions workflow from `1.21.x` to `1.22.x` Signed-off-by: Bo-Yi Wu * ci: improve CI Robustness and Test Reliability - Add `-race` flag to the test-tags list in GitHub Actions workflow configuration Signed-off-by: Bo-Yi Wu --------- Signed-off-by: appleboy Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 9 +++++---- .github/workflows/goreleaser.yml | 6 ++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 75e6d05..9ab00ae 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -24,15 +24,16 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v3.7.0 with: - version: v1.55.2 + version: v1.56.1 args: --verbose test: needs: lint strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.18", "1.19", "1.20", "1.21"] - test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json"] + go: ["1.18", "1.19", "1.20", "1.21", "1.22"] + test-tags: + ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: - os: ubuntu-latest go-build: ~/.cache/go-build @@ -74,5 +75,5 @@ jobs: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - name: Format - if: matrix.go-version == '1.21.x' + if: matrix.go-version == '1.22.x' run: diff -u <(echo -n) <(gofmt -d .) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index cbd5d41..8ae1182 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -16,13 +16,11 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - - name: Set up Go + - name: Set up Go uses: actions/setup-go@v5 with: go-version: "^1" - - - name: Run GoReleaser + - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 with: # either 'goreleaser' (default) or 'goreleaser-pro' From ecdbbbe9483dd12222f2085f717a2c7cb5ac55fe Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 19 Feb 2024 10:34:48 +0800 Subject: [PATCH 821/912] chore: refactor CI and update dependencies (#3848) - Update GitHub Actions workflow to use a unified step for checking out the repository and setting up Go with dynamic versioning - Upgrade golangci-lint-action version from v3.7.0 to v4 and bump the lint version from v1.56.1 to v1.56.2 - Update dependencies in go.mod: sonic from v1.10.2 to v1.11.0, validator from v10.17.0 to v10.18.0, x/net from v0.20.0 to v0.21.0, go-urn from v1.3.0 to v1.4.0, x/crypto from v0.18.0 to v0.19.0, and x/sys from v0.16.0 to v0.17.0 Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 15 +++++++++------ go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 9ab00ae..3fe007f 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -15,16 +15,19 @@ jobs: lint: runs-on: ubuntu-latest steps: - - name: Setup go + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "^1.18" - - name: Checkout repository - uses: actions/checkout@v4 + go-version-file: "go.mod" + check-latest: true - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v3.7.0 + uses: golangci/golangci-lint-action@v4 with: - version: v1.56.1 + version: v1.56.2 args: --verbose test: needs: lint diff --git a/go.mod b/go.mod index 0b60c5d..fbbce7c 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,16 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.10.2 + github.com/bytedance/sonic v1.11.0 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.17.0 + github.com/go-playground/validator/v10 v10.18.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.1.1 github.com/stretchr/testify v1.8.4 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.20.0 + golang.org/x/net v0.21.0 google.golang.org/protobuf v1.32.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -25,13 +25,13 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect - github.com/leodido/go-urn v1.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index e360d9d..ce6c7fe 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= -github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.0 h1:FwNNv6Vu4z2Onf1++LNzxB/QhitD8wuTdpZzMTGITWo= +github.com/bytedance/sonic v1.11.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -21,8 +21,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= -github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= +github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -33,8 +33,8 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/leodido/go-urn v1.3.0 h1:jX8FDLfW4ThVXctBNZ+3cIWnCSnrACDV73r76dy0aQQ= -github.com/leodido/go-urn v1.3.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -63,14 +63,14 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 739d2d9c80e0298dafb5df1c30bae35d63935d6c Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 5 Mar 2024 14:07:11 +0800 Subject: [PATCH 822/912] chore(perf): Optimize the Copy method of the Context struct (#3859) * Optimize the Copy method of the Context struct: using 'make' to initialize the map('cp.Keys') with a length of 'c.Keys'; avoiding repeatedly assiging the 'params' to 'context'. * Using temporary variables to save c.Keys and c.Params to prevent them from changing during the copying process. --------- Co-authored-by: huangzw --- context.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 420ff16..126d35d 100644 --- a/context.go +++ b/context.go @@ -113,20 +113,24 @@ func (c *Context) Copy() *Context { cp := Context{ writermem: c.writermem, Request: c.Request, - Params: c.Params, engine: c.engine, } + cp.writermem.ResponseWriter = nil cp.Writer = &cp.writermem cp.index = abortIndex cp.handlers = nil - cp.Keys = map[string]any{} - for k, v := range c.Keys { + + cKeys := c.Keys + cp.Keys = make(map[string]any, len(cKeys)) + for k, v := range cKeys { cp.Keys[k] = v } - paramCopy := make([]Param, len(cp.Params)) - copy(paramCopy, cp.Params) - cp.Params = paramCopy + + cParams := c.Params + cp.Params = make([]Param, len(cParams)) + copy(cp.Params, cParams) + return &cp } From ae15646aba14cd8245fbebd263cc7740c6789ef3 Mon Sep 17 00:00:00 2001 From: guangwu Date: Tue, 5 Mar 2024 14:36:02 +0800 Subject: [PATCH 823/912] test(http): use constant instead of numeric literal (#3863) Signed-off-by: guoguangwu --- routes_test.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/routes_test.go b/routes_test.go index a0ff695..185abd9 100644 --- a/routes_test.go +++ b/routes_test.go @@ -180,58 +180,58 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) assert.Equal(t, "/api/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) - assert.Equal(t, 200, w.Code) + assert.Equal(t, http.StatusOK, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api#?"}) assert.Equal(t, "/api/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) assert.Equal(t, "/api/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../api"}) assert.Equal(t, "/api/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../api"}) assert.Equal(t, "/api/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../"}) assert.Equal(t, "//path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "api/../../../"}) assert.Equal(t, "/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "../../gin-gonic.com"}) assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path2", header{Key: "X-Forwarded-Prefix", Value: "/../../gin-gonic.com"}) assert.Equal(t, "/gin-goniccom/path2/", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "https://gin-gonic.com/#"}) assert.Equal(t, "https/gin-goniccom/https/gin-goniccom/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "#api"}) assert.Equal(t, "api/api/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/#?a=1"}) assert.Equal(t, "/nor-mal/a1/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) w = PerformRequest(router, http.MethodGet, "/path/", header{Key: "X-Forwarded-Prefix", Value: "/nor-mal/%2e%2e/"}) assert.Equal(t, "/nor-mal/2e2e/path", w.Header().Get("Location")) - assert.Equal(t, 301, w.Code) + assert.Equal(t, http.StatusMovedPermanently, w.Code) router.RedirectTrailingSlash = false @@ -619,11 +619,11 @@ func TestRouterNotFound(t *testing.T) { router = New() router.NoRoute(func(c *Context) { if c.Request.RequestURI == "/login" { - c.String(200, "login") + c.String(http.StatusOK, "login") } }) router.GET("/logout", func(c *Context) { - c.String(200, "logout") + c.String(http.StatusOK, "logout") }) w = PerformRequest(router, http.MethodGet, "/login") assert.Equal(t, "login", w.Body.String()) @@ -635,7 +635,7 @@ func TestRouterStaticFSNotFound(t *testing.T) { router := New() router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) router.NoRoute(func(c *Context) { - c.String(404, "non existent") + c.String(http.StatusNotFound, "non existent") }) w := PerformRequest(router, http.MethodGet, "/nonexistent") @@ -718,12 +718,12 @@ func TestRouteRawPathNoUnescape(t *testing.T) { func TestRouteServeErrorWithWriteHeader(t *testing.T) { route := New() route.Use(func(c *Context) { - c.Status(421) + c.Status(http.StatusMisdirectedRequest) c.Next() }) w := PerformRequest(route, http.MethodGet, "/NotFound") - assert.Equal(t, 421, w.Code) + assert.Equal(t, http.StatusMisdirectedRequest, w.Code) assert.Equal(t, 0, w.Body.Len()) } From 9c61295efeea99f6c9d1722294f1bf61d8e464d6 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Tue, 5 Mar 2024 14:54:35 +0100 Subject: [PATCH 824/912] chore(header): Add support for RFC 9512: application/yaml (#3851) * fix(binding): support application/yaml RFC 9512 defines application/yaml as the official YAML MIME type. application/x-yaml is deprecated. In this commit, we ensure it is recognized correctly in Content-Type. * fix(render): use application/yaml when rendering YAML As per RFC 9512, application/x-yaml is now deprecated and applications should use application/yaml. This commit fix the Content-Type header when rendering YAML. --- binding/binding.go | 3 ++- binding/binding_nomsgpack.go | 3 ++- binding/binding_test.go | 2 ++ context_test.go | 6 +++--- render/render_test.go | 4 ++-- render/yaml.go | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 4094852..036b329 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -21,6 +21,7 @@ const ( MIMEMSGPACK = "application/x-msgpack" MIMEMSGPACK2 = "application/msgpack" MIMEYAML = "application/x-yaml" + MIMEYAML2 = "application/yaml" MIMETOML = "application/toml" ) @@ -102,7 +103,7 @@ func Default(method, contentType string) Binding { return ProtoBuf case MIMEMSGPACK, MIMEMSGPACK2: return MsgPack - case MIMEYAML: + case MIMEYAML, MIMEYAML2: return YAML case MIMETOML: return TOML diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index 93ad8ba..552a86b 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -19,6 +19,7 @@ const ( MIMEMultipartPOSTForm = "multipart/form-data" MIMEPROTOBUF = "application/x-protobuf" MIMEYAML = "application/x-yaml" + MIMEYAML2 = "application/yaml" MIMETOML = "application/toml" ) @@ -96,7 +97,7 @@ func Default(method, contentType string) Binding { return XML case MIMEPROTOBUF: return ProtoBuf - case MIMEYAML: + case MIMEYAML, MIMEYAML2: return YAML case MIMEMultipartPOSTForm: return FormMultipart diff --git a/binding/binding_test.go b/binding/binding_test.go index 9af4f88..feb8eed 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -164,6 +164,8 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, YAML, Default("POST", MIMEYAML)) assert.Equal(t, YAML, Default("PUT", MIMEYAML)) + assert.Equal(t, YAML, Default("POST", MIMEYAML2)) + assert.Equal(t, YAML, Default("PUT", MIMEYAML2)) assert.Equal(t, TOML, Default("POST", MIMETOML)) assert.Equal(t, TOML, Default("PUT", MIMETOML)) diff --git a/context_test.go b/context_test.go index 88165c0..d060ccf 100644 --- a/context_test.go +++ b/context_test.go @@ -1060,7 +1060,7 @@ func TestContextRenderUTF8Attachment(t *testing.T) { } // TestContextRenderYAML tests that the response is serialized as YAML -// and Content-Type is set to application/x-yaml +// and Content-Type is set to application/yaml func TestContextRenderYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -1069,7 +1069,7 @@ func TestContextRenderYAML(t *testing.T) { assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } // TestContextRenderTOML tests that the response is serialized as TOML @@ -1217,7 +1217,7 @@ func TestContextNegotiationWithYAML(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "foo: bar\n", w.Body.String()) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } func TestContextNegotiationWithTOML(t *testing.T) { diff --git a/render/render_test.go b/render/render_test.go index c9db635..145f131 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -280,12 +280,12 @@ b: d: [3, 4] ` (YAML{data}).WriteContentType(w) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) err := (YAML{data}).Render(w) assert.NoError(t, err) assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String()) - assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) + assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } type fail struct{} diff --git a/render/yaml.go b/render/yaml.go index fc927c1..042bb82 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -15,7 +15,7 @@ type YAML struct { Data any } -var yamlContentType = []string{"application/x-yaml; charset=utf-8"} +var yamlContentType = []string{"application/yaml; charset=utf-8"} // Render (YAML) marshals the given interface object and writes data with custom ContentType. func (r YAML) Render(w http.ResponseWriter) error { From f75144a356e57c95bd21a048f0a40492dcdb33c5 Mon Sep 17 00:00:00 2001 From: guangwu Date: Tue, 5 Mar 2024 21:55:25 +0800 Subject: [PATCH 825/912] docs: fix typo in comment (#3868) Signed-off-by: guoguangwu --- routes_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes_test.go b/routes_test.go index 185abd9..73f393e 100644 --- a/routes_test.go +++ b/routes_test.go @@ -339,7 +339,7 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) { // TestRouteParamsNotEmpty tests that context parameters will be set // even if a route with params/wildcards is registered after the context -// initialisation (which happened in a previous requets). +// initialisation (which happened in a previous requests). func TestRouteParamsNotEmpty(t *testing.T) { name := "" lastName := "" From 09f8224593e31edf3c58ab3f13bc31ef53473733 Mon Sep 17 00:00:00 2001 From: Karthik Reddy Puli <47525322+KarthikReddyPuli@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:16:53 +0530 Subject: [PATCH 826/912] fix(route): Add fullPath in context copy (#3784) * fix: Add fullPath in context copy * Update context.go --------- Co-authored-by: Bo-Yi Wu --- context.go | 1 + context_test.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/context.go b/context.go index 126d35d..609827d 100644 --- a/context.go +++ b/context.go @@ -120,6 +120,7 @@ func (c *Context) Copy() *Context { cp.Writer = &cp.writermem cp.index = abortIndex cp.handlers = nil + cp.fullPath = c.fullPath cKeys := c.Keys cp.Keys = make(map[string]any, len(cKeys)) diff --git a/context_test.go b/context_test.go index d060ccf..ac766e2 100644 --- a/context_test.go +++ b/context_test.go @@ -324,6 +324,7 @@ func TestContextCopy(t *testing.T) { c.handlers = HandlersChain{func(c *Context) {}} c.Params = Params{Param{Key: "foo", Value: "bar"}} c.Set("foo", "bar") + c.fullPath = "/hola" cp := c.Copy() assert.Nil(t, cp.handlers) @@ -336,6 +337,7 @@ func TestContextCopy(t *testing.T) { assert.Equal(t, cp.Params, c.Params) cp.Set("foo", "notBar") assert.False(t, cp.Keys["foo"] == c.Keys["foo"]) + assert.Equal(t, cp.fullPath, c.fullPath) } func TestContextHandlerName(t *testing.T) { From 3ea8bd99fbb4e499d70a0c8e1ce2ce4b7c6348b6 Mon Sep 17 00:00:00 2001 From: jessetang <1430482733@qq.com> Date: Wed, 6 Mar 2024 22:27:21 +0800 Subject: [PATCH 827/912] chore(refactor): modify interface check way (#3855) Signed-off-by: demoManito <1430482733@qq.com> --- render/render.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/render/render.go b/render/render.go index 7955000..4bdcfa2 100644 --- a/render/render.go +++ b/render/render.go @@ -15,22 +15,22 @@ type Render interface { } var ( - _ Render = JSON{} - _ Render = IndentedJSON{} - _ Render = SecureJSON{} - _ Render = JsonpJSON{} - _ Render = XML{} - _ Render = String{} - _ Render = Redirect{} - _ Render = Data{} - _ Render = HTML{} - _ HTMLRender = HTMLDebug{} - _ HTMLRender = HTMLProduction{} - _ Render = YAML{} - _ Render = Reader{} - _ Render = AsciiJSON{} - _ Render = ProtoBuf{} - _ Render = TOML{} + _ Render = (*JSON)(nil) + _ Render = (*IndentedJSON)(nil) + _ Render = (*SecureJSON)(nil) + _ Render = (*JsonpJSON)(nil) + _ Render = (*XML)(nil) + _ Render = (*String)(nil) + _ Render = (*Redirect)(nil) + _ Render = (*Data)(nil) + _ Render = (*HTML)(nil) + _ HTMLRender = (*HTMLDebug)(nil) + _ HTMLRender = (*HTMLProduction)(nil) + _ Render = (*YAML)(nil) + _ Render = (*Reader)(nil) + _ Render = (*AsciiJSON)(nil) + _ Render = (*ProtoBuf)(nil) + _ Render = (*TOML)(nil) ) func writeContentType(w http.ResponseWriter, value []string) { From 97eab7d09a8b048cab4a3d8ebd6c0ea78284c716 Mon Sep 17 00:00:00 2001 From: jessetang <1430482733@qq.com> Date: Fri, 8 Mar 2024 15:56:00 +0800 Subject: [PATCH 828/912] test(git): gitignore add develop tools (#3370) --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index bdd50c9..1ea0e2b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ count.out test profile.out tmp.out + +# Develop tools +.idea/ +.vscode/ From 5f458dd1a6d631f324e4af9a4f5429ffdf199342 Mon Sep 17 00:00:00 2001 From: Endless Paradox <129645532+EndlessParadox1@users.noreply.github.com> Date: Mon, 11 Mar 2024 22:22:58 +0800 Subject: [PATCH 829/912] feat(auth): add proxy-server authentication (#3877) --- auth.go | 21 +++++++++++++++++++++ auth_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/auth.go b/auth.go index 2503c51..cc6c5a7 100644 --- a/auth.go +++ b/auth.go @@ -15,6 +15,7 @@ import ( // AuthUserKey is the cookie name for user credential in basic auth. const AuthUserKey = "user" +const AuthProxyUserKey = "proxy_user" // Accounts defines a key/value for user/pass list of authorized logins. type Accounts map[string]string @@ -89,3 +90,23 @@ func authorizationHeader(user, password string) string { base := user + ":" + password return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base)) } + +func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc { + if realm == "" { + realm = "Proxy Authorization Required" + } + realm = "Basic realm=" + strconv.Quote(realm) + pairs := processAccounts(accounts) + return func(c *Context) { + proxyUser, found := pairs.searchCredential(c.requestHeader("Proxy-Authorization")) + if !found { + // Credentials doesn't match, we return 407 and abort handlers chain. + c.Header("Proxy-Authenticate", realm) + c.AbortWithStatus(http.StatusProxyAuthRequired) + return + } + // The proxy_user credentials was found, set proxy_user's id to key AuthProxyUserKey in this context, the proxy_user's id can be read later using + // c.MustGet(gin.AuthProxyUserKey). + c.Set(AuthProxyUserKey, proxyUser) + } +} diff --git a/auth_test.go b/auth_test.go index 42b6f8f..f717592 100644 --- a/auth_test.go +++ b/auth_test.go @@ -137,3 +137,40 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "Basic realm=\"My Custom \\\"Realm\\\"\"", w.Header().Get("WWW-Authenticate")) } + +func TestBasicAuthForProxySucceed(t *testing.T) { + accounts := Accounts{"admin": "password"} + router := New() + router.Use(BasicAuthForProxy(accounts, "")) + router.Any("/*proxyPath", func(c *Context) { + c.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string)) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("Proxy-Authorization", authorizationHeader("admin", "password")) + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "admin", w.Body.String()) +} + +func TestBasicAuthForProxy407(t *testing.T) { + called := false + accounts := Accounts{"foo": "bar"} + router := New() + router.Use(BasicAuthForProxy(accounts, "")) + router.Any("/*proxyPath", func(c *Context) { + called = true + c.String(http.StatusOK, c.MustGet(AuthProxyUserKey).(string)) + }) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) + router.ServeHTTP(w, req) + + assert.False(t, called) + assert.Equal(t, http.StatusProxyAuthRequired, w.Code) + assert.Equal(t, "Basic realm=\"Proxy Authorization Required\"", w.Header().Get("Proxy-Authenticate")) +} From 646312aef6a34095476ac846b0920db5fb24b2ea Mon Sep 17 00:00:00 2001 From: qingmu Date: Mon, 11 Mar 2024 22:24:36 +0800 Subject: [PATCH 830/912] fix: protect Context.Keys map when call Copy method (#3873) --- context.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/context.go b/context.go index 609827d..59abee1 100644 --- a/context.go +++ b/context.go @@ -124,9 +124,11 @@ func (c *Context) Copy() *Context { cKeys := c.Keys cp.Keys = make(map[string]any, len(cKeys)) + c.mu.RLock() for k, v := range cKeys { cp.Keys[k] = v } + c.mu.RUnlock() cParams := c.Params cp.Params = make([]Param, len(cParams)) From 83fc7673f9797b4c7d8d1c41b94e9922303e6275 Mon Sep 17 00:00:00 2001 From: TotomiEcio <63461656+TotomiEcio@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:25:28 -0300 Subject: [PATCH 831/912] docs: fix typo in function documentation (#3872) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 59abee1..a17a58e 100644 --- a/context.go +++ b/context.go @@ -393,7 +393,7 @@ func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) // // router.GET("/user/:id", func(c *gin.Context) { // // a GET request to /user/john -// id := c.Param("id") // id == "/john" +// id := c.Param("id") // id == "john" // // a GET request to /user/john/ // id := c.Param("id") // id == "/john/" // }) From ac5e84d93ce34359bfd2f346cb2971ea754d83e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Mon, 11 Mar 2024 22:35:30 +0800 Subject: [PATCH 832/912] feat(engine): Added `OptionFunc` and `With` (#3572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Added `OptionFunc` and `With` * fix: `With(opts...)` must be after `New` * feat: improve New with * fix: test * optimize code * optimize nolint * optimize code Signed-off-by: Flc゛ --------- Signed-off-by: Flc゛ --- context_test.go | 8 ++++---- gin.go | 20 ++++++++++++++++---- gin_test.go | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/context_test.go b/context_test.go index ac766e2..33cc43f 100644 --- a/context_test.go +++ b/context_test.go @@ -1000,7 +1000,7 @@ func TestContextRenderFile(t *testing.T) { c.File("./gin.go") assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' assert.NotEqual(t, "", w.Header().Get("Content-Type")) @@ -1014,7 +1014,7 @@ func TestContextRenderFileFromFS(t *testing.T) { c.FileFromFS("./gin.go", Dir(".", false)) assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' assert.NotEqual(t, "", w.Header().Get("Content-Type")) @@ -1030,7 +1030,7 @@ func TestContextRenderAttachment(t *testing.T) { c.FileAttachment("./gin.go", newFilename) assert.Equal(t, 200, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition")) } @@ -1057,7 +1057,7 @@ func TestContextRenderUTF8Attachment(t *testing.T) { c.FileAttachment("./gin.go", newFilename) assert.Equal(t, 200, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") assert.Equal(t, `attachment; filename*=UTF-8''`+url.QueryEscape(newFilename), w.Header().Get("Content-Disposition")) } diff --git a/gin.go b/gin.go index 24a9864..1633fe1 100644 --- a/gin.go +++ b/gin.go @@ -47,6 +47,9 @@ var regRemoveRepeatedChar = regexp.MustCompile("/{2,}") // HandlerFunc defines the handler used by gin middleware as return value. type HandlerFunc func(*Context) +// OptionFunc defines the function to change the default configuration +type OptionFunc func(*Engine) + // HandlersChain defines a HandlerFunc slice. type HandlersChain []HandlerFunc @@ -182,7 +185,7 @@ var _ IRouter = (*Engine)(nil) // - ForwardedByClientIP: true // - UseRawPath: false // - UnescapePathValues: true -func New() *Engine { +func New(opts ...OptionFunc) *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ @@ -211,15 +214,15 @@ func New() *Engine { engine.pool.New = func() any { return engine.allocateContext(engine.maxParams) } - return engine + return engine.With(opts...) } // Default returns an Engine instance with the Logger and Recovery middleware already attached. -func Default() *Engine { +func Default(opts ...OptionFunc) *Engine { debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) - return engine + return engine.With(opts...) } func (engine *Engine) Handler() http.Handler { @@ -313,6 +316,15 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { return engine } +// With returns a new Engine instance with the provided options. +func (engine *Engine) With(opts ...OptionFunc) *Engine { + for _, opt := range opts { + opt(engine) + } + + return engine +} + func (engine *Engine) rebuild404Handlers() { engine.allNoRoute = engine.combineHandlers(engine.noRoute) } diff --git a/gin_test.go b/gin_test.go index 8825ac7..4550a7e 100644 --- a/gin_test.go +++ b/gin_test.go @@ -696,3 +696,37 @@ func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) func handlerTest1(c *Context) {} func handlerTest2(c *Context) {} + +func TestNewOptionFunc(t *testing.T) { + var fc = func(e *Engine) { + e.GET("/test1", handlerTest1) + e.GET("/test2", handlerTest2) + + e.Use(func(c *Context) { + c.Next() + }) + } + + r := New(fc) + + routes := r.Routes() + assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) +} + +func TestWithOptionFunc(t *testing.T) { + r := New() + + r.With(func(e *Engine) { + e.GET("/test1", handlerTest1) + e.GET("/test2", handlerTest2) + + e.Use(func(c *Context) { + c.Next() + }) + }) + + routes := r.Routes() + assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) +} From 1b3c0859693fc85290c01ba098b1440d4776549f Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 11 Mar 2024 10:41:07 -0400 Subject: [PATCH 833/912] chore(debug): add ability to override the debugPrint statement (#2337) * feat: add ability to override the debugPrint statement This allows users to use a single logger within their application for all printing, regardless of level. * chore: make the code more readable, as per review comment * fix: use tab instead of space for indentation --- debug.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/debug.go b/debug.go index 1fc0caf..1761fe3 100644 --- a/debug.go +++ b/debug.go @@ -23,6 +23,9 @@ func IsDebugging() bool { // DebugPrintRouteFunc indicates debug log output format. var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) +// DebugPrintFunc indicates debug log output format. +var DebugPrintFunc func(format string, values ...interface{}) + func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { if IsDebugging() { nuHandlers := len(handlers) @@ -48,12 +51,19 @@ func debugPrintLoadTemplate(tmpl *template.Template) { } func debugPrint(format string, values ...any) { - if IsDebugging() { - if !strings.HasSuffix(format, "\n") { - format += "\n" - } - fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...) + if !IsDebugging() { + return + } + + if DebugPrintFunc != nil { + DebugPrintFunc(format, values...) + return + } + + if !strings.HasSuffix(format, "\n") { + format += "\n" } + fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...) } func getMinVer(v string) (uint64, error) { From ab8042e9e5370bbe0e93ea5adc6e74ae4c5df95e Mon Sep 17 00:00:00 2001 From: Noah Yao Date: Mon, 11 Mar 2024 22:44:28 +0800 Subject: [PATCH 834/912] chore(request): check reader if it's nil before reading (#3419) --- context.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/context.go b/context.go index a17a58e..0c73a49 100644 --- a/context.go +++ b/context.go @@ -880,6 +880,9 @@ func (c *Context) GetHeader(key string) string { // GetRawData returns stream data. func (c *Context) GetRawData() ([]byte, error) { + if c.Request.Body == nil { + return nil, errors.New("cannot read nil body") + } return io.ReadAll(c.Request.Body) } From f70dd00b00bc0a46cb18b55bfe1f918d5d29b511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Tue, 12 Mar 2024 13:49:23 +0800 Subject: [PATCH 835/912] fix(engine): fix unit test (#3878) * fix(engine): fix unit test * fix(engine): fix unit test --- context_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context_test.go b/context_test.go index 33cc43f..089047c 100644 --- a/context_test.go +++ b/context_test.go @@ -1044,7 +1044,7 @@ func TestContextRenderAndEscapeAttachment(t *testing.T) { c.FileAttachment("./gin.go", maliciousFilename) assert.Equal(t, 200, w.Code) - assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Contains(t, w.Body.String(), "func New(opts ...OptionFunc) *Engine {") assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", actualEscapedResponseFilename), w.Header().Get("Content-Disposition")) } From 861ffb9181dc811dc5d76fc450b36d3e68850b95 Mon Sep 17 00:00:00 2001 From: Endless Paradox <129645532+EndlessParadox1@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:51:04 +0800 Subject: [PATCH 836/912] docs(middleware): comments to function `BasicAuthForProxy` (#3881) --- auth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/auth.go b/auth.go index cc6c5a7..2ed33ac 100644 --- a/auth.go +++ b/auth.go @@ -91,6 +91,7 @@ func authorizationHeader(user, password string) string { return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base)) } +// BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware. func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc { if realm == "" { realm = "Proxy Authorization Required" From 990c44aebf20f0796d99051e53d6ee75b7ed52fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Tue, 12 Mar 2024 13:55:52 +0800 Subject: [PATCH 837/912] docs(context): Added deprecation comments to BindWith (#3880) --- deprecated.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deprecated.go b/deprecated.go index 9521308..b4c6cd8 100644 --- a/deprecated.go +++ b/deprecated.go @@ -12,6 +12,8 @@ import ( // BindWith binds the passed struct pointer using the specified binding engine. // See the binding package. +// +// Deprecated: Use MustBindWith or ShouldBindWith. func (c *Context) BindWith(obj any, b binding.Binding) error { log.Println(`BindWith(\"any, binding.Binding\") error is going to be deprecated, please check issue #662 and either use MustBindWith() if you From ee70b30a97205ac1f32889f41d8a494b3b2c81a5 Mon Sep 17 00:00:00 2001 From: Endless Paradox <129645532+EndlessParadox1@users.noreply.github.com> Date: Wed, 13 Mar 2024 23:22:05 +0800 Subject: [PATCH 838/912] docs: Add document to constant `AuthProxyUserKey` and `BasicAuthForProxy`. (#3887) --- auth.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/auth.go b/auth.go index 2ed33ac..5d3222d 100644 --- a/auth.go +++ b/auth.go @@ -15,6 +15,8 @@ import ( // AuthUserKey is the cookie name for user credential in basic auth. const AuthUserKey = "user" + +// AuthProxyUserKey is the cookie name for proxy_user credential in basic auth for proxy. const AuthProxyUserKey = "proxy_user" // Accounts defines a key/value for user/pass list of authorized logins. @@ -92,6 +94,7 @@ func authorizationHeader(user, password string) string { } // BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware. +// If the realm is empty, "Proxy Authorization Required" will be used by default. func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc { if realm == "" { realm = "Proxy Authorization Required" From fd60a24ab76c3c92955ba253c1f7eda9e4981c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Thu, 14 Mar 2024 11:22:54 +0800 Subject: [PATCH 839/912] test(path): Optimize unit test execution results (#3883) * test(path): Add a GC recycle validation * test(path): Optimize unit test execution results * test(path): Optimize unit test execution results --- path_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/path_test.go b/path_test.go index caefd63..864302f 100644 --- a/path_test.go +++ b/path_test.go @@ -6,6 +6,7 @@ package gin import ( + "runtime" "strings" "testing" @@ -80,6 +81,10 @@ func TestPathCleanMallocs(t *testing.T) { t.Skip("skipping malloc count in short mode") } + if runtime.GOMAXPROCS(0) > 1 { + t.Skip("skipping malloc count; GOMAXPROCS>1") + } + for _, test := range cleanTests { allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) }) assert.EqualValues(t, allocs, 0) From 0d9dbbb44551a872d30fd89d4d55ba0515d646fd Mon Sep 17 00:00:00 2001 From: Guilherme Aleixo Date: Mon, 18 Mar 2024 11:14:06 -0300 Subject: [PATCH 840/912] chore(security): upgrade Protobuf for CVE-2024-24786 (#3893) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fbbce7c..11ce23e 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.21.0 - google.golang.org/protobuf v1.32.0 + google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index ce6c7fe..49eae34 100644 --- a/go.sum +++ b/go.sum @@ -74,8 +74,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 2b1da2b0b38dfc5d5841266037c0c8b249eca1dd Mon Sep 17 00:00:00 2001 From: "Farmer.Chillax" <48387781+FarmerChillax@users.noreply.github.com> Date: Thu, 21 Mar 2024 21:08:41 +0800 Subject: [PATCH 841/912] fix(context): make context Value method adhere to Go standards (#3897) --- context.go | 6 +++++- context_test.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 0c73a49..3a9608d 100644 --- a/context.go +++ b/context.go @@ -43,6 +43,10 @@ const BodyBytesKey = "_gin-gonic/gin/bodybyteskey" // ContextKey is the key that a Context returns itself for. const ContextKey = "_gin-gonic/gin/contextkey" +type ContextKeyType int + +const ContextRequestKey ContextKeyType = 0 + // abortIndex represents a typical value used in abort functions. const abortIndex int8 = math.MaxInt8 >> 1 @@ -1225,7 +1229,7 @@ func (c *Context) Err() error { // if no value is associated with key. Successive calls to Value with // the same key returns the same result. func (c *Context) Value(key any) any { - if key == 0 { + if key == ContextRequestKey { return c.Request } if key == ContextKey { diff --git a/context_test.go b/context_test.go index 089047c..9c1717e 100644 --- a/context_test.go +++ b/context_test.go @@ -1985,7 +1985,7 @@ func TestContextGolangContext(t *testing.T) { ti, ok := c.Deadline() assert.Equal(t, ti, time.Time{}) assert.False(t, ok) - assert.Equal(t, c.Value(0), c.Request) + assert.Equal(t, c.Value(ContextRequestKey), c.Request) assert.Equal(t, c.Value(ContextKey), c) assert.Nil(t, c.Value("foo")) From 78f4687875d72d10392f8a77008cbefdec4c0aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Thu, 21 Mar 2024 21:13:56 +0800 Subject: [PATCH 842/912] build(codecov): Added a codecov configuration (#3891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Flc゛ --- codecov.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..47782e5 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +coverage: + require_ci_to_pass: true + + status: + project: + default: + target: 99% + threshold: 99% + + patch: + default: + target: 99% + threshold: 95% \ No newline at end of file From 8790d08909fc4d193c6c787c9c72f3089168f411 Mon Sep 17 00:00:00 2001 From: illiafox <61962654+illiafox@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:28:42 +0200 Subject: [PATCH 843/912] fix(uri): query binding bug (#3236) * fix query mapping * query binding test --- binding/query.go | 4 ++-- binding/query_test.go | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 binding/query_test.go diff --git a/binding/query.go b/binding/query.go index c958b88..baa4aea 100644 --- a/binding/query.go +++ b/binding/query.go @@ -12,9 +12,9 @@ func (queryBinding) Name() string { return "query" } -func (queryBinding) Bind(req *http.Request, obj any) error { +func (q queryBinding) Bind(req *http.Request, obj any) error { values := req.URL.Query() - if err := mapForm(obj, values); err != nil { + if err := mapFormByTag(obj, values, q.Name()); err != nil { return err } return validate(obj) diff --git a/binding/query_test.go b/binding/query_test.go new file mode 100644 index 0000000..7210204 --- /dev/null +++ b/binding/query_test.go @@ -0,0 +1,23 @@ +package binding + +import ( + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryBinding(t *testing.T) { + var s struct { + Foo string `query:"foo"` + } + + request := &http.Request{URL: &url.URL{RawQuery: "foo=BAR"}} + + err := queryBinding{}.Bind(request, &s) + require.NoError(t, err) + + assert.Equal(t, "BAR", s.Foo) +} From d4e413648824333726ef65de5defc457e9dbf095 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 22 Mar 2024 10:01:27 +0800 Subject: [PATCH 844/912] Revert "fix(uri): query binding bug (#3236)" (#3899) This reverts commit 8790d08909fc4d193c6c787c9c72f3089168f411. --- binding/query.go | 4 ++-- binding/query_test.go | 23 ----------------------- 2 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 binding/query_test.go diff --git a/binding/query.go b/binding/query.go index baa4aea..c958b88 100644 --- a/binding/query.go +++ b/binding/query.go @@ -12,9 +12,9 @@ func (queryBinding) Name() string { return "query" } -func (q queryBinding) Bind(req *http.Request, obj any) error { +func (queryBinding) Bind(req *http.Request, obj any) error { values := req.URL.Query() - if err := mapFormByTag(obj, values, q.Name()); err != nil { + if err := mapForm(obj, values); err != nil { return err } return validate(obj) diff --git a/binding/query_test.go b/binding/query_test.go deleted file mode 100644 index 7210204..0000000 --- a/binding/query_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package binding - -import ( - "net/http" - "net/url" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestQueryBinding(t *testing.T) { - var s struct { - Foo string `query:"foo"` - } - - request := &http.Request{URL: &url.URL{RawQuery: "foo=BAR"}} - - err := queryBinding{}.Bind(request, &s) - require.NoError(t, err) - - assert.Equal(t, "BAR", s.Foo) -} From fd1faaded01aef14a3955ec076f1cbeb9cb87775 Mon Sep 17 00:00:00 2001 From: ssfyn Date: Sat, 23 Mar 2024 08:50:25 +0800 Subject: [PATCH 845/912] feat(binding): support override default binding implement (#3514) --- binding/binding.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 036b329..9472387 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -73,18 +73,18 @@ var Validator StructValidator = &defaultValidator{} // These implement the Binding interface and can be used to bind the data // present in the request to struct instances. var ( - JSON = jsonBinding{} - XML = xmlBinding{} - Form = formBinding{} - Query = queryBinding{} - FormPost = formPostBinding{} - FormMultipart = formMultipartBinding{} - ProtoBuf = protobufBinding{} - MsgPack = msgpackBinding{} - YAML = yamlBinding{} - Uri = uriBinding{} - Header = headerBinding{} - TOML = tomlBinding{} + JSON BindingBody = jsonBinding{} + XML BindingBody = xmlBinding{} + Form Binding = formBinding{} + Query Binding = queryBinding{} + FormPost Binding = formPostBinding{} + FormMultipart Binding = formMultipartBinding{} + ProtoBuf BindingBody = protobufBinding{} + MsgPack BindingBody = msgpackBinding{} + YAML BindingBody = yamlBinding{} + Uri BindingUri = uriBinding{} + Header Binding = headerBinding{} + TOML BindingBody = tomlBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method From 7a865dcf1dbe6ec52e074b1ddce830d278eb72cf Mon Sep 17 00:00:00 2001 From: RedCrazyGhost <49381700+RedCrazyGhost@users.noreply.github.com> Date: Sat, 23 Mar 2024 22:09:02 +0800 Subject: [PATCH 846/912] feat(bind): ShouldBindBodyWith shortcut and change doc (#3871) * feat: ShouldBindBodyWith shortcut and change doc * fix: yaml can parse json test case * style: fix new test case in context_test.go * chore: modify the code style to specify binding type * chroe: gofmt modifies the code format --- context.go | 20 ++++ context_test.go | 257 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/doc.md | 9 +- 3 files changed, 284 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index 3a9608d..afc3c35 100644 --- a/context.go +++ b/context.go @@ -774,6 +774,26 @@ func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error return bb.BindBody(body, obj) } +// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). +func (c *Context) ShouldBindBodyWithJSON(obj any) error { + return c.ShouldBindBodyWith(obj, binding.JSON) +} + +// ShouldBindBodyWithXML is a shortcut for c.ShouldBindBodyWith(obj, binding.XML). +func (c *Context) ShouldBindBodyWithXML(obj any) error { + return c.ShouldBindBodyWith(obj, binding.XML) +} + +// ShouldBindBodyWithYAML is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML). +func (c *Context) ShouldBindBodyWithYAML(obj any) error { + return c.ShouldBindBodyWith(obj, binding.YAML) +} + +// ShouldBindBodyWithTOML is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML). +func (c *Context) ShouldBindBodyWithTOML(obj any) error { + return c.ShouldBindBodyWith(obj, binding.TOML) +} + // ClientIP implements one best effort algorithm to return the real client IP. // It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. // If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). diff --git a/context_test.go b/context_test.go index 9c1717e..e9bbae5 100644 --- a/context_test.go +++ b/context_test.go @@ -1977,6 +1977,263 @@ func TestContextShouldBindBodyWith(t *testing.T) { } } +func TestContextShouldBindBodyWithJSON(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " JSON & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " JSON & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " JSON & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " JSON & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo=FOO`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeJSON struct { + Foo string `json:"foo" binding:"required"` + } + objJSON := typeJSON{} + + if tt.bindingBody == binding.JSON { + assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{"FOO"}, objJSON) + } + + if tt.bindingBody == binding.XML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + + if tt.bindingBody == binding.YAML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + + if tt.bindingBody == binding.TOML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + } +} + +func TestContextShouldBindBodyWithXML(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " XML & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " XML & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " XML & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " XML & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo=FOO`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeXML struct { + Foo string `xml:"foo" binding:"required"` + } + objXML := typeXML{} + + if tt.bindingBody == binding.JSON { + assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + assert.Equal(t, typeXML{}, objXML) + } + + if tt.bindingBody == binding.XML { + assert.NoError(t, c.ShouldBindBodyWithXML(&objXML)) + assert.Equal(t, typeXML{"FOO"}, objXML) + } + + if tt.bindingBody == binding.YAML { + assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + assert.Equal(t, typeXML{}, objXML) + } + + if tt.bindingBody == binding.TOML { + assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + assert.Equal(t, typeXML{}, objXML) + } + } +} + +func TestContextShouldBindBodyWithYAML(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " YAML & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " YAML & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " YAML & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " YAML & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo=FOO`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeYAML struct { + Foo string `yaml:"foo" binding:"required"` + } + objYAML := typeYAML{} + + // YAML belongs to a super collection of JSON, so JSON can be parsed by YAML + if tt.bindingBody == binding.JSON { + assert.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) + assert.Equal(t, typeYAML{"FOO"}, objYAML) + } + + if tt.bindingBody == binding.XML { + assert.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) + assert.Equal(t, typeYAML{}, objYAML) + } + + if tt.bindingBody == binding.YAML { + assert.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) + assert.Equal(t, typeYAML{"FOO"}, objYAML) + } + + if tt.bindingBody == binding.TOML { + assert.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) + assert.Equal(t, typeYAML{}, objYAML) + } + } +} + +func TestContextShouldBindBodyWithTOML(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " TOML & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " TOML & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " TOML & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " TOML & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo = 'FOO'`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeTOML struct { + Foo string `toml:"foo" binding:"required"` + } + objTOML := typeTOML{} + + if tt.bindingBody == binding.JSON { + assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + assert.Equal(t, typeTOML{}, objTOML) + } + + if tt.bindingBody == binding.XML { + assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + assert.Equal(t, typeTOML{}, objTOML) + } + + if tt.bindingBody == binding.YAML { + assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + assert.Equal(t, typeTOML{}, objTOML) + } + + if tt.bindingBody == binding.TOML { + assert.NoError(t, c.ShouldBindBodyWithTOML(&objTOML)) + assert.Equal(t, typeTOML{"FOO"}, objTOML) + } + } +} + func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) diff --git a/docs/doc.md b/docs/doc.md index df006e8..70c9f27 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -1956,7 +1956,12 @@ func SomeHandler(c *gin.Context) { } ``` -For this, you can use `c.ShouldBindBodyWith`. +For this, you can use `c.ShouldBindBodyWith` or shortcuts. + +- `c.ShouldBindBodyWithJSON` is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). +- `c.ShouldBindBodyWithXML` is a shortcut for c.ShouldBindBodyWith(obj, binding.XML). +- `c.ShouldBindBodyWithYAML` is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML). +- `c.ShouldBindBodyWithTOML` is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML). ```go func SomeHandler(c *gin.Context) { @@ -1969,7 +1974,7 @@ func SomeHandler(c *gin.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 { + } else if errB2 := c.ShouldBindBodyWithXML(&objB); errB2 == nil { c.String(http.StatusOK, `the body should be formB XML`) } else { ... From c964ad370bbe007f1b18a7570f058a66f05fbe1f Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 1 Apr 2024 12:58:01 +0800 Subject: [PATCH 847/912] chore(optimize): the ShouldBindUri method of the Context struct (#3911) Co-authored-by: huangzw --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index afc3c35..391adaf 100644 --- a/context.go +++ b/context.go @@ -739,7 +739,7 @@ func (c *Context) ShouldBindHeader(obj any) error { // ShouldBindUri binds the passed struct pointer using the specified binding engine. func (c *Context) ShouldBindUri(obj any) error { - m := make(map[string][]string) + m := make(map[string][]string, len(c.Params)) for _, v := range c.Params { m[v.Key] = []string{v.Value} } From 56dc72c4d5b1076fc9c6b81f57299739c11910b8 Mon Sep 17 00:00:00 2001 From: imalasong <55082705+imalasong@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:58:00 +0800 Subject: [PATCH 848/912] ci(Makefile): vet command add .PHONY (#3915) --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index ebde4ee..b58f24f 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ fmt-check: exit 1; \ fi; +.PHONY: vet vet: $(GO) vet $(VETPACKAGES) From 8acbe657f1c140e3fba38f869978cab2376500c9 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 2 Apr 2024 16:20:48 +0800 Subject: [PATCH 849/912] ci(release): refactor changelog regex patterns and exclusions (#3914) * chore: refactor changelog regex patterns and exclusions - Update the build configuration to skip the build using a comment - Change the `changelog` use from `git` to `github` - Modify the regex patterns for `Features`, `Bug fixes`, and `Enhancements` titles in the changelog - Add a new regex pattern for the `Refactor` title in the changelog - Update the excluded items in the changelog to include `docs` and `CICD` with corrected quotes Signed-off-by: appleboy * chore: update configuration file field names - Change the `skip` field to `disable` in the `.goreleaser.yaml` file. Signed-off-by: appleboy * update Signed-off-by: appleboy * chore: refine changelog categorization rules - Update regular expressions for `feat`, `fix`, and `chore` categories in `.goreleaser.yaml` - Remove `Refactor` category from changelog configuration - Add `Refactor` category with updated regular expression and order to changelog configuration Signed-off-by: Bo-Yi Wu --------- Signed-off-by: appleboy Signed-off-by: Bo-Yi Wu --- .goreleaser.yaml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e435e56..1cc0bee 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,8 +1,7 @@ project_name: gin builds: - - - # If true, skip the build. + - # If true, skip the build. # Useful for library projects. # Default is false skip: true @@ -10,7 +9,7 @@ builds: changelog: # Set it to true if you wish to skip the changelog generation. # This may result in an empty release notes on GitHub/GitLab/Gitea. - skip: false + disable: false # Changelog generation implementation to use. # @@ -21,7 +20,7 @@ changelog: # - `github-native`: uses the GitHub release notes generation API, disables the groups feature. # # Defaults to `git`. - use: git + use: github # Sorts the changelog by the commit's messages. # Could either be asc, desc or empty @@ -38,12 +37,15 @@ changelog: - title: Features regexp: "^.*feat[(\\w)]*:+.*$" order: 0 - - title: 'Bug fixes' + - title: "Bug fixes" regexp: "^.*fix[(\\w)]*:+.*$" order: 1 - - title: 'Enhancements' + - title: "Enhancements" regexp: "^.*chore[(\\w)]*:+.*$" order: 2 + - title: "Refactor" + regexp: "^.*refactor[(\\w)]*:+.*$" + order: 3 - title: Others order: 999 @@ -52,6 +54,6 @@ changelog: # the changelog # Default is empty exclude: - - '^docs' - - 'CICD' + - "^docs" + - "CICD" - typo From c6f90df4e0c888c69524307cc35952ec2e7ead41 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 2 Apr 2024 18:57:22 +0800 Subject: [PATCH 850/912] chore: update various Go dependencies to latest versions (#3901) - Update `github.com/bytedance/sonic` from v1.11.0 to v1.11.3 - Update `github.com/go-playground/validator/v10` from v10.18.0 to v10.19.0 - Update `github.com/pelletier/go-toml/v2` from v2.1.1 to v2.2.0 - Update `github.com/stretchr/testify` from v1.8.4 to v1.9.0 - Update `golang.org/x/net` from v0.21.0 to v0.22.0 - Update `golang.org/x/crypto` from v0.19.0 to v0.21.0 - Update `golang.org/x/sys` from v0.17.0 to v0.18.0 Signed-off-by: appleboy --- go.mod | 16 ++++++++-------- go.sum | 32 +++++++++++++++++--------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 11ce23e..13342ac 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,16 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.11.0 + github.com/bytedance/sonic v1.11.3 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.18.0 + github.com/go-playground/validator/v10 v10.19.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 - github.com/pelletier/go-toml/v2 v2.1.1 - github.com/stretchr/testify v1.8.4 + github.com/pelletier/go-toml/v2 v2.2.0 + github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.21.0 + golang.org/x/net v0.22.0 google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -24,14 +24,14 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.19.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index 49eae34..7a3aa22 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.11.0 h1:FwNNv6Vu4z2Onf1++LNzxB/QhitD8wuTdpZzMTGITWo= -github.com/bytedance/sonic v1.11.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= +github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -21,8 +21,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= -github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -30,8 +30,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= -github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= @@ -42,20 +42,22 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= @@ -63,14 +65,14 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= From 0397e5e0c0f8f8176c29f7edd8f1bff8e45df780 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 7 Apr 2024 10:18:23 +0800 Subject: [PATCH 851/912] chore: update changelog categories and improve documentation (#3917) - Add new changelog categories for "Build process updates" and "Documentation updates" Signed-off-by: appleboy --- .goreleaser.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 1cc0bee..ac2b462 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -46,6 +46,12 @@ changelog: - title: "Refactor" regexp: "^.*refactor[(\\w)]*:+.*$" order: 3 + - title: "Build process updates" + regexp: ^.*?(build|ci)(\(.+\))??!?:.+$ + order: 4 + - title: "Documentation updates" + regexp: ^.*?docs?(\(.+\))??!?:.+$ + order: 4 - title: Others order: 999 From f80ade7a4b85b116d294c7610a89819905fa44d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 07:07:01 +0800 Subject: [PATCH 852/912] chore(deps): bump golangci/golangci-lint-action from 4 to 5 (#3941) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4 to 5. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v4...v5) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 3fe007f..2e43434 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -25,7 +25,7 @@ jobs: go-version-file: "go.mod" check-latest: true - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v5 with: version: v1.56.2 args: --verbose From b4f66e965ba9d60257e0de4c25d4ad4bd6115927 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 07:07:23 +0800 Subject: [PATCH 853/912] chore(deps): bump github.com/bytedance/sonic from 1.11.3 to 1.11.6 (#3940) Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.11.3 to 1.11.6. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.11.3...v1.11.6) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 7 ++++--- go.sum | 19 ++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 13342ac..44e02a3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.20 require ( - github.com/bytedance/sonic v1.11.3 + github.com/bytedance/sonic v1.11.6 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.19.0 github.com/goccy/go-json v0.10.2 @@ -18,8 +18,9 @@ require ( ) require ( - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect diff --git a/go.sum b/go.sum index 7a3aa22..2028e88 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,11 @@ -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= -github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= -github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From a18219566ca25fc51e6d2886bed849c6c3a0cd12 Mon Sep 17 00:00:00 2001 From: dkkb <82504881+dkkb@users.noreply.github.com> Date: Tue, 7 May 2024 09:43:15 +0800 Subject: [PATCH 854/912] feat(binding): Support custom BindUnmarshaler for binding. (#3933) --- binding/form_mapping.go | 20 ++++++++ binding/form_mapping_test.go | 99 ++++++++++++++++++++++++++++++++++++ docs/doc.md | 41 +++++++++++++++ gin_test.go | 24 +++++++++ 4 files changed, 184 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 77a1bde..db235e5 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -165,6 +165,23 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter return setter.TrySet(value, field, tagValue, setOpt) } +// BindUnmarshaler is the interface used to wrap the UnmarshalParam method. +type BindUnmarshaler interface { + // UnmarshalParam decodes and assigns a value from an form or query param. + UnmarshalParam(param string) error +} + +// trySetCustom tries to set a custom type value +// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true` +// to skip the default value setting. +func trySetCustom(val string, value reflect.Value) (isSet bool, err error) { + switch v := value.Addr().Interface().(type) { + case BindUnmarshaler: + return true, v.UnmarshalParam(val) + } + return false, nil +} + func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) { vs, ok := form[tagValue] if !ok && !opt.isDefaultExists { @@ -194,6 +211,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ if len(vs) > 0 { val = vs[0] } + if ok, err := trySetCustom(val, value); ok { + return ok, err + } return true, setWithProperType(val, value, field) } } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 16527eb..ed01a08 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -5,8 +5,11 @@ package binding import ( + "fmt" "mime/multipart" "reflect" + "strconv" + "strings" "testing" "time" @@ -323,3 +326,99 @@ func TestMappingIgnoredCircularRef(t *testing.T) { err := mappingByPtr(&s, formSource{}, "form") assert.NoError(t, err) } + +type customUnmarshalParamHex int + +func (f *customUnmarshalParamHex) UnmarshalParam(param string) error { + v, err := strconv.ParseInt(param, 16, 64) + if err != nil { + return err + } + *f = customUnmarshalParamHex(v) + return nil +} + +func TestMappingCustomUnmarshalParamHexWithFormTag(t *testing.T) { + var s struct { + Foo customUnmarshalParamHex `form:"foo"` + } + err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "form") + assert.NoError(t, err) + + assert.EqualValues(t, 245, s.Foo) +} + +func TestMappingCustomUnmarshalParamHexWithURITag(t *testing.T) { + var s struct { + Foo customUnmarshalParamHex `uri:"foo"` + } + err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "uri") + assert.NoError(t, err) + + assert.EqualValues(t, 245, s.Foo) +} + +type customUnmarshalParamType struct { + Protocol string + Path string + Name string +} + +func (f *customUnmarshalParamType) UnmarshalParam(param string) error { + parts := strings.Split(param, ":") + if len(parts) != 3 { + return fmt.Errorf("invalid format") + } + f.Protocol = parts[0] + f.Path = parts[1] + f.Name = parts[2] + return nil +} + +func TestMappingCustomStructTypeWithFormTag(t *testing.T) { + var s struct { + FileData customUnmarshalParamType `form:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") + assert.NoError(t, err) + + assert.EqualValues(t, "file", s.FileData.Protocol) + assert.EqualValues(t, "/foo", s.FileData.Path) + assert.EqualValues(t, "happiness", s.FileData.Name) +} + +func TestMappingCustomStructTypeWithURITag(t *testing.T) { + var s struct { + FileData customUnmarshalParamType `uri:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") + assert.NoError(t, err) + + assert.EqualValues(t, "file", s.FileData.Protocol) + assert.EqualValues(t, "/foo", s.FileData.Path) + assert.EqualValues(t, "happiness", s.FileData.Name) +} + +func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) { + var s struct { + FileData *customUnmarshalParamType `form:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") + assert.NoError(t, err) + + assert.EqualValues(t, "file", s.FileData.Protocol) + assert.EqualValues(t, "/foo", s.FileData.Path) + assert.EqualValues(t, "happiness", s.FileData.Name) +} + +func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { + var s struct { + FileData *customUnmarshalParamType `uri:"data"` + } + err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") + assert.NoError(t, err) + + assert.EqualValues(t, "file", s.FileData.Protocol) + assert.EqualValues(t, "/foo", s.FileData.Path) + assert.EqualValues(t, "happiness", s.FileData.Name) +} diff --git a/docs/doc.md b/docs/doc.md index 70c9f27..177c447 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -27,6 +27,7 @@ - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind Uri](#bind-uri) + - [Bind custom unmarshaler](#bind-custom-unmarshaler) - [Bind Header](#bind-header) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) @@ -899,6 +900,46 @@ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 curl -v localhost:8088/thinkerou/not-uuid ``` +### Bind custom unmarshaler + +```go +package main + +import ( + "github.com/gin-gonic/gin" + "strings" +) + +type Birthday string + +func (b *Birthday) UnmarshalParam(param string) error { + *b = Birthday(strings.Replace(param, "-", "/", -1)) + return nil +} + +func main() { + route := gin.Default() + var request struct { + Birthday Birthday `form:"birthday"` + } + route.GET("/test", func(ctx *gin.Context) { + _ = ctx.BindQuery(&request) + ctx.JSON(200, request.Birthday) + }) + route.Run(":8088") +} +``` + +Test it with: + +```sh +curl 'localhost:8088/test?birthday=2000-01-01' +``` +Result +```sh +"2000/01/01" +``` + ### Bind Header ```go diff --git a/gin_test.go b/gin_test.go index 4550a7e..e68f1ce 100644 --- a/gin_test.go +++ b/gin_test.go @@ -14,6 +14,7 @@ import ( "net/http/httptest" "reflect" "strconv" + "strings" "sync/atomic" "testing" "time" @@ -730,3 +731,26 @@ func TestWithOptionFunc(t *testing.T) { assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) } + +type Birthday string + +func (b *Birthday) UnmarshalParam(param string) error { + *b = Birthday(strings.Replace(param, "-", "/", -1)) + return nil +} + +func TestCustomUnmarshalStruct(t *testing.T) { + route := Default() + var request struct { + Birthday Birthday `form:"birthday"` + } + route.GET("/test", func(ctx *Context) { + _ = ctx.BindQuery(&request) + ctx.JSON(200, request.Birthday) + }) + req := httptest.NewRequest("GET", "/test?birthday=2000-01-01", nil) + w := httptest.NewRecorder() + route.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + assert.Equal(t, `"2000/01/01"`, w.Body.String()) +} From 638aa19e7d30513f7bc777c62ff8558fd5f90ea5 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 7 May 2024 10:15:53 +0800 Subject: [PATCH 855/912] chore: update external dependencies to latest versions (#3950) - Update `github.com/go-playground/validator/v10` from v10.19.0 to v10.20.0 - Update `github.com/pelletier/go-toml/v2` from v2.2.0 to v2.2.2 - Update `golang.org/x/net` from v0.22.0 to v0.25.0 - Update `google.golang.org/protobuf` from v1.33.0 to v1.34.1 - Update `golang.org/x/arch` from v0.7.0 to v0.8.0 - Update `golang.org/x/crypto` from v0.21.0 to v0.23.0 - Update `golang.org/x/sys` from v0.18.0 to v0.20.0 - Update `golang.org/x/text` from v0.14.0 to v0.15.0 Signed-off-by: Bo-Yi Wu --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 44e02a3..3e94e50 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,15 @@ go 1.20 require ( github.com/bytedance/sonic v1.11.6 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.19.0 + github.com/go-playground/validator/v10 v10.20.0 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 - github.com/pelletier/go-toml/v2 v2.2.0 + github.com/pelletier/go-toml/v2 v2.2.2 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.22.0 - google.golang.org/protobuf v1.33.0 + golang.org/x/net v0.25.0 + google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -31,8 +31,8 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect ) diff --git a/go.sum b/go.sum index 2028e88..ce905e7 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= -github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= @@ -39,8 +39,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -60,21 +60,21 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= -golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 39089af62535b27aa63608f341c0a339aa88f64e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 7 May 2024 10:16:38 +0800 Subject: [PATCH 856/912] chore: refactor configuration files for better readability (#3951) - Remove filters from the `changelog` section in `.goreleaser.yaml` Signed-off-by: Bo-Yi Wu --- .goreleaser.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index ac2b462..99b66fe 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -54,12 +54,3 @@ changelog: order: 4 - title: Others order: 999 - - filters: - # Commit messages matching the regexp listed here will be removed from - # the changelog - # Default is empty - exclude: - - "^docs" - - "CICD" - - typo From 75ccf94d605a05fe24817fc2f166f6f2959d5cea Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 7 May 2024 11:23:42 +0800 Subject: [PATCH 857/912] feat: update version constant to v1.10.0 (#3952) - Update the version constant from "v1.9.1" to "v1.10.0" Signed-off-by: Bo-Yi Wu --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 85462e5..93ad965 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.9.1" +const Version = "v1.10.0" From 490accf5d7d49138f0af806318826d92513b1395 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 7 May 2024 12:50:01 +0800 Subject: [PATCH 858/912] docs: update documentation and release notes for Gin v1.10.0 (#3953) * docs: update documentation and release notes for Gin v1.10.0 - Add release notes for Gin v1.10.0 - Include new features and bug fixes in the changelog - Document enhancements and build process updates - Update documentation for context and middleware functions - Upgrade dependencies and optimize unit tests Signed-off-by: Bo-Yi Wu * feat: refactor CI, enhance file binding, and update dependencies - Add proxy-server authentication feature - Add support for custom BindUnmarshaler for binding - Fix binding error while not uploading file - Refactor CI and update dependencies - Add support for RFC 9512: application/yaml - Optimize the Copy method of the Context struct - Update various Go dependencies to latest versions Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu --- CHANGELOG.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7968520..de47c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,82 @@ # Gin ChangeLog +## Gin v1.10.0 + +### Features + +* feat(auth): add proxy-server authentication (#3877) (@EndlessParadox1) +* feat(bind): ShouldBindBodyWith shortcut and change doc (#3871) (@RedCrazyGhost) +* feat(binding): Support custom BindUnmarshaler for binding. (#3933) (@dkkb) +* feat(binding): support override default binding implement (#3514) (@ssfyn) +* feat(engine): Added `OptionFunc` and `With` (#3572) (@flc1125) +* feat(logger): ability to skip logs based on user-defined logic (#3593) (@palvaneh) + +### Bug fixes + +* Revert "fix(uri): query binding bug (#3236)" (#3899) (@appleboy) +* fix(binding): binding error while not upload file (#3819) (#3820) (@clearcodecn) +* fix(binding): dereference pointer to struct (#3199) (@echovl) +* fix(context): make context Value method adhere to Go standards (#3897) (@FarmerChillax) +* fix(engine): fix unit test (#3878) (@flc1125) +* fix(header): Allow header according to RFC 7231 (HTTP 405) (#3759) (@Crocmagnon) +* fix(route): Add fullPath in context copy (#3784) (@KarthikReddyPuli) +* fix(router): catch-all conflicting wildcard (#3812) (@FirePing32) +* fix(sec): upgrade golang.org/x/crypto to 0.17.0 (#3832) (@chncaption) +* fix(tree): correctly expand the capacity of params (#3502) (@georgijd-form3) +* fix(uri): query binding bug (#3236) (@illiafox) +* fix: Add pointer support for url query params (#3659) (#3666) (@omkar-foss) +* fix: protect Context.Keys map when call Copy method (#3873) (@kingcanfish) + +### Enhancements + +* chore(CI): update release args (#3595) (@qloog) +* chore(IP): add TrustedPlatform constant for Fly.io. (#3839) (@ab) +* chore(debug): add ability to override the debugPrint statement (#2337) (@josegonzalez) +* chore(deps): update dependencies to latest versions (#3835) (@appleboy) +* chore(header): Add support for RFC 9512: application/yaml (#3851) (@vincentbernat) +* chore(http): use white color for HTTP 1XX (#3741) (@viralparmarme) +* chore(optimize): the ShouldBindUri method of the Context struct (#3911) (@1911860538) +* chore(perf): Optimize the Copy method of the Context struct (#3859) (@1911860538) +* chore(refactor): modify interface check way (#3855) (@demoManito) +* chore(request): check reader if it's nil before reading (#3419) (@noahyao1024) +* chore(security): upgrade Protobuf for CVE-2024-24786 (#3893) (@Fotkurz) +* chore: refactor CI and update dependencies (#3848) (@appleboy) +* chore: refactor configuration files for better readability (#3951) (@appleboy) +* chore: update GitHub Actions configuration (#3792) (@appleboy) +* chore: update changelog categories and improve documentation (#3917) (@appleboy) +* chore: update dependencies to latest versions (#3694) (@appleboy) +* chore: update external dependencies to latest versions (#3950) (@appleboy) +* chore: update various Go dependencies to latest versions (#3901) (@appleboy) + +### Build process updates + +* build(codecov): Added a codecov configuration (#3891) (@flc1125) +* ci(Makefile): vet command add .PHONY (#3915) (@imalasong) +* ci(lint): update tooling and workflows for consistency (#3834) (@appleboy) +* ci(release): refactor changelog regex patterns and exclusions (#3914) (@appleboy) +* ci(testing): add go1.22 version (#3842) (@appleboy) + +### Documentation updates + +* docs(context): Added deprecation comments to BindWith (#3880) (@flc1125) +* docs(middleware): comments to function `BasicAuthForProxy` (#3881) (@EndlessParadox1) +* docs: Add document to constant `AuthProxyUserKey` and `BasicAuthForProxy`. (#3887) (@EndlessParadox1) +* docs: fix typo in comment (#3868) (@testwill) +* docs: fix typo in function documentation (#3872) (@TotomiEcio) +* docs: remove redundant comments (#3765) (@WeiTheShinobi) +* feat: update version constant to v1.10.0 (#3952) (@appleboy) + +### Others + +* Upgrade golang.org/x/net -> v0.13.0 (#3684) (@cpcf) +* test(git): gitignore add develop tools (#3370) (@demoManito) +* test(http): use constant instead of numeric literal (#3863) (@testwill) +* test(path): Optimize unit test execution results (#3883) (@flc1125) +* test(render): increased unit tests coverage (#3691) (@araujo88) + ## Gin v1.9.1 -### BUG FIXES +### BUG FIXES * fix Request.Context() checks [#3512](https://github.com/gin-gonic/gin/pull/3512) From e60113dc9531779a15dcb761655d986a56273ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flc=E3=82=9B?= Date: Wed, 8 May 2024 05:29:54 +0800 Subject: [PATCH 859/912] docs(engine): fix comments for the `With` (#3955) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Flc゛ --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 1633fe1..8f32378 100644 --- a/gin.go +++ b/gin.go @@ -316,7 +316,7 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { return engine } -// With returns a new Engine instance with the provided options. +// With returns a Engine with the configuration set in the OptionFunc. func (engine *Engine) With(opts ...OptionFunc) *Engine { for _, opt := range opts { opt(engine) From 8dd088927ab50b3b37be1f7ba14931c8eddafe07 Mon Sep 17 00:00:00 2001 From: lgbgbl <65756378+lgbgbl@users.noreply.github.com> Date: Wed, 8 May 2024 06:28:15 +0800 Subject: [PATCH 860/912] refactor(binding): use strings.Cut to replace strings.Index (#3522) --- binding/form_mapping.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index db235e5..108606f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -397,11 +397,8 @@ func setTimeDuration(val string, value reflect.Value) error { } func head(str, sep string) (head string, tail string) { - idx := strings.Index(str, sep) - if idx < 0 { - return str, "" - } - return str[:idx], str[idx+len(sep):] + head, tail, _ = strings.Cut(str, sep) + return head, tail } func setFormMap(ptr any, form map[string][]string) error { From f5f5da8fa09d12a22225c493fa8191fb14bdd5bf Mon Sep 17 00:00:00 2001 From: Pedro Aguiar <72931357+codespearhead@users.noreply.github.com> Date: Tue, 7 May 2024 18:31:01 -0400 Subject: [PATCH 861/912] docs(gin): update link to dont-trust-all-proxies section (#3938) (#3945) Update link [1] to [2] after PR #3449 was merged. [1] https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies [2] https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies Closes --- gin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gin.go b/gin.go index 8f32378..03edbff 100644 --- a/gin.go +++ b/gin.go @@ -391,7 +391,7 @@ func (engine *Engine) Run(addr ...string) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } address := resolveAddress(addr) @@ -512,7 +512,7 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler()) From 7d147928ee232fce156ea7ce8ae6329e148aeb41 Mon Sep 17 00:00:00 2001 From: Kostadin Plachkov <20207730+kplachkov@users.noreply.github.com> Date: Wed, 8 May 2024 04:13:36 +0300 Subject: [PATCH 862/912] fix(gin): data race warning for gin mode (#1580) * fix: data race warning (#1180) * Fix the tests * refactor: remove unnecessary imports and optimize codebase - Remove unnecessary import of `flag` Signed-off-by: Bo-Yi Wu * test: refactor test assertions for mode settings - Update test assertions for mode setting in `mode_test.go` Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu Co-authored-by: Bo-Yi Wu --- debug.go | 3 ++- mode.go | 20 +++++++++----------- mode_test.go | 19 ++++++------------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/debug.go b/debug.go index 1761fe3..0d808f1 100644 --- a/debug.go +++ b/debug.go @@ -10,6 +10,7 @@ import ( "runtime" "strconv" "strings" + "sync/atomic" ) const ginSupportMinGoVer = 18 @@ -17,7 +18,7 @@ const ginSupportMinGoVer = 18 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { - return ginMode == debugCode + return atomic.LoadInt32(&ginMode) == debugCode } // DebugPrintRouteFunc indicates debug log output format. diff --git a/mode.go b/mode.go index fd26d90..13aa3be 100644 --- a/mode.go +++ b/mode.go @@ -8,6 +8,7 @@ import ( "flag" "io" "os" + "sync/atomic" "github.com/gin-gonic/gin/binding" ) @@ -43,10 +44,8 @@ var DefaultWriter io.Writer = os.Stdout // DefaultErrorWriter is the default io.Writer used by Gin to debug errors var DefaultErrorWriter io.Writer = os.Stderr -var ( - ginMode = debugCode - modeName = DebugMode -) +var ginMode int32 = debugCode +var modeName atomic.Value func init() { mode := os.Getenv(EnvGinMode) @@ -64,17 +63,16 @@ func SetMode(value string) { } switch value { - case DebugMode: - ginMode = debugCode + case DebugMode, "": + atomic.StoreInt32(&ginMode, debugCode) case ReleaseMode: - ginMode = releaseCode + atomic.StoreInt32(&ginMode, releaseCode) case TestMode: - ginMode = testCode + atomic.StoreInt32(&ginMode, testCode) default: panic("gin mode unknown: " + value + " (available mode: debug release test)") } - - modeName = value + modeName.Store(value) } // DisableBindValidation closes the default validator. @@ -96,5 +94,5 @@ func EnableJsonDecoderDisallowUnknownFields() { // Mode returns current gin mode. func Mode() string { - return modeName + return modeName.Load().(string) } diff --git a/mode_test.go b/mode_test.go index 2407f46..be03a9d 100644 --- a/mode_test.go +++ b/mode_test.go @@ -5,8 +5,8 @@ package gin import ( - "flag" "os" + "sync/atomic" "testing" "github.com/gin-gonic/gin/binding" @@ -18,31 +18,24 @@ func init() { } func TestSetMode(t *testing.T) { - assert.Equal(t, testCode, ginMode) + assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, TestMode, Mode()) os.Unsetenv(EnvGinMode) SetMode("") - assert.Equal(t, testCode, ginMode) + assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, TestMode, Mode()) - tmp := flag.CommandLine - flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError) - SetMode("") - assert.Equal(t, debugCode, ginMode) - assert.Equal(t, DebugMode, Mode()) - flag.CommandLine = tmp - SetMode(DebugMode) - assert.Equal(t, debugCode, ginMode) + assert.Equal(t, int32(debugCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, DebugMode, Mode()) SetMode(ReleaseMode) - assert.Equal(t, releaseCode, ginMode) + assert.Equal(t, int32(releaseCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, ReleaseMode, Mode()) SetMode(TestMode) - assert.Equal(t, testCode, ginMode) + assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode)) assert.Equal(t, TestMode, Mode()) assert.Panics(t, func() { SetMode("unknown") }) From b1c1e7b572f76071fb0e0e7884a0697e0458aa7c Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 8 May 2024 10:14:42 +0800 Subject: [PATCH 863/912] ci: update Go version requirements and remove test files (#3957) - Update the Go version requirements in `.github/workflows/gin.yml` - Remove test files for Go versions 1.18 and 1.19 - Update the required Go version in `debug.go` and `debug_test.go` - Rename and modify files related to Go version 1.19 and 1.20 in the `internal/bytesconv` directory Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 2 +- context_1.18_test.go | 37 ------------------- context_1.19_test.go | 30 --------------- context_test.go | 13 +++++++ debug.go | 2 +- debug_test.go | 2 +- .../{bytesconv_1.20.go => bytesconv.go} | 2 - internal/bytesconv/bytesconv_1.19.go | 26 ------------- 8 files changed, 16 insertions(+), 98 deletions(-) delete mode 100644 context_1.18_test.go delete mode 100644 context_1.19_test.go rename internal/bytesconv/{bytesconv_1.20.go => bytesconv.go} (97%) delete mode 100644 internal/bytesconv/bytesconv_1.19.go diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 2e43434..4a9dbc9 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -34,7 +34,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.18", "1.19", "1.20", "1.21", "1.22"] + go: ["1.20", "1.21", "1.22"] test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: diff --git a/context_1.18_test.go b/context_1.18_test.go deleted file mode 100644 index 6118bea..0000000 --- a/context_1.18_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2021 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.19 - -package gin - -import ( - "bytes" - "mime/multipart" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestContextFormFileFailed18(t *testing.T) { - buf := new(bytes.Buffer) - mw := multipart.NewWriter(buf) - defer func(mw *multipart.Writer) { - err := mw.Close() - if err != nil { - assert.Error(t, err) - } - }(mw) - c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Set("Content-Type", mw.FormDataContentType()) - c.engine.MaxMultipartMemory = 8 << 20 - assert.Panics(t, func() { - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) - }) -} diff --git a/context_1.19_test.go b/context_1.19_test.go deleted file mode 100644 index dd75325..0000000 --- a/context_1.19_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2022 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build go1.19 - -package gin - -import ( - "bytes" - "mime/multipart" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestContextFormFileFailed19(t *testing.T) { - buf := new(bytes.Buffer) - mw := multipart.NewWriter(buf) - mw.Close() - c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Set("Content-Type", mw.FormDataContentType()) - c.engine.MaxMultipartMemory = 8 << 20 - f, err := c.FormFile("file") - assert.Error(t, err) - assert.Nil(t, f) -} diff --git a/context_test.go b/context_test.go index e9bbae5..ae34c65 100644 --- a/context_test.go +++ b/context_test.go @@ -90,6 +90,19 @@ func TestContextFormFile(t *testing.T) { assert.NoError(t, c.SaveUploadedFile(f, "test")) } +func TestContextFormFileFailed(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + c.engine.MaxMultipartMemory = 8 << 20 + f, err := c.FormFile("file") + assert.Error(t, err) + assert.Nil(t, f) +} + func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) diff --git a/debug.go b/debug.go index 0d808f1..ae346e9 100644 --- a/debug.go +++ b/debug.go @@ -78,7 +78,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.18+. + debugPrint(`[WARNING] Now Gin requires Go 1.20+. `) } diff --git a/debug_test.go b/debug_test.go index 2d5e9a5..e390972 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.18+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.20+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/internal/bytesconv/bytesconv_1.20.go b/internal/bytesconv/bytesconv.go similarity index 97% rename from internal/bytesconv/bytesconv_1.20.go rename to internal/bytesconv/bytesconv.go index 5b6040a..a02c53c 100644 --- a/internal/bytesconv/bytesconv_1.20.go +++ b/internal/bytesconv/bytesconv.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build go1.20 - package bytesconv import ( diff --git a/internal/bytesconv/bytesconv_1.19.go b/internal/bytesconv/bytesconv_1.19.go deleted file mode 100644 index 669c9c9..0000000 --- a/internal/bytesconv/bytesconv_1.19.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -//go:build !go1.20 - -package bytesconv - -import ( - "unsafe" -) - -// StringToBytes converts string to byte slice without a memory allocation. -func StringToBytes(s string) []byte { - return *(*[]byte)(unsafe.Pointer( - &struct { - string - Cap int - }{s, len(s)}, - )) -} - -// BytesToString converts byte slice to string without a memory allocation. -func BytesToString(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} From 8791c96960e719ff2f41e24163c5898656cee474 Mon Sep 17 00:00:00 2001 From: Johannes Eiglsperger Date: Wed, 8 May 2024 09:47:54 +0200 Subject: [PATCH 864/912] feat(fs): Export, test and document OnlyFilesFS (#3939) --- fs.go | 50 +++++++++++++++++++---------------- fs_test.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ routergroup.go | 2 +- 3 files changed, 100 insertions(+), 23 deletions(-) create mode 100644 fs_test.go diff --git a/fs.go b/fs.go index f17d743..51c3db8 100644 --- a/fs.go +++ b/fs.go @@ -9,37 +9,43 @@ import ( "os" ) -type onlyFilesFS struct { - fs http.FileSystem +// OnlyFilesFS implements an http.FileSystem without `Readdir` functionality. +type OnlyFilesFS struct { + FileSystem http.FileSystem } -type neuteredReaddirFile struct { +// Open passes `Open` to the upstream implementation without `Readdir` functionality. +func (o OnlyFilesFS) Open(name string) (http.File, error) { + f, err := o.FileSystem.Open(name) + + if err != nil { + return nil, err + } + + return neutralizedReaddirFile{f}, nil +} + +// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`. +type neutralizedReaddirFile struct { http.File } -// Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally -// in router.Static(). -// if listDirectory == true, then it works the same as http.Dir() otherwise it returns -// a filesystem that prevents http.FileServer() to list the directory files. +// Readdir overrides the http.File default implementation and always returns nil. +func (n neutralizedReaddirFile) Readdir(_ int) ([]os.FileInfo, error) { + // this disables directory listing + return nil, nil +} + +// Dir returns an http.FileSystem that can be used by http.FileServer(). +// It is used internally in router.Static(). +// if listDirectory == true, then it works the same as http.Dir(), +// otherwise it returns a filesystem that prevents http.FileServer() to list the directory files. func Dir(root string, listDirectory bool) http.FileSystem { fs := http.Dir(root) + if listDirectory { return fs } - return &onlyFilesFS{fs} -} - -// Open conforms to http.Filesystem. -func (fs onlyFilesFS) Open(name string) (http.File, error) { - f, err := fs.fs.Open(name) - if err != nil { - return nil, err - } - return neuteredReaddirFile{f}, nil -} -// Readdir overrides the http.File default implementation. -func (f neuteredReaddirFile) Readdir(_ int) ([]os.FileInfo, error) { - // this disables directory listing - return nil, nil + return &OnlyFilesFS{FileSystem: fs} } diff --git a/fs_test.go b/fs_test.go new file mode 100644 index 0000000..a1690cd --- /dev/null +++ b/fs_test.go @@ -0,0 +1,71 @@ +package gin + +import ( + "errors" + "net/http" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +type mockFileSystem struct { + open func(name string) (http.File, error) +} + +func (m *mockFileSystem) Open(name string) (http.File, error) { + return m.open(name) +} + +func TestOnlyFilesFS_Open(t *testing.T) { + var testFile *os.File + mockFS := &mockFileSystem{ + open: func(name string) (http.File, error) { + return testFile, nil + }, + } + fs := &OnlyFilesFS{FileSystem: mockFS} + + file, err := fs.Open("foo") + + assert.NoError(t, err) + assert.Equal(t, testFile, file.(neutralizedReaddirFile).File) +} + +func TestOnlyFilesFS_Open_err(t *testing.T) { + testError := errors.New("mock") + mockFS := &mockFileSystem{ + open: func(_ string) (http.File, error) { + return nil, testError + }, + } + fs := &OnlyFilesFS{FileSystem: mockFS} + + file, err := fs.Open("foo") + + assert.ErrorIs(t, err, testError) + assert.Nil(t, file) +} + +func Test_neuteredReaddirFile_Readdir(t *testing.T) { + n := neutralizedReaddirFile{} + + res, err := n.Readdir(0) + + assert.NoError(t, err) + assert.Nil(t, res) +} + +func TestDir_listDirectory(t *testing.T) { + testRoot := "foo" + fs := Dir(testRoot, true) + + assert.Equal(t, http.Dir(testRoot), fs) +} + +func TestDir(t *testing.T) { + testRoot := "foo" + fs := Dir(testRoot, false) + + assert.Equal(t, &OnlyFilesFS{FileSystem: http.Dir(testRoot)}, fs) +} diff --git a/routergroup.go b/routergroup.go index c833fe8..b2540ec 100644 --- a/routergroup.go +++ b/routergroup.go @@ -218,7 +218,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { - if _, noListing := fs.(*onlyFilesFS); noListing { + if _, noListing := fs.(*OnlyFilesFS); noListing { c.Writer.WriteHeader(http.StatusNotFound) } From 3ac729dc4a497d360a23b9d7e766c622b3c99f51 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Thu, 9 May 2024 09:17:06 +0800 Subject: [PATCH 865/912] feat(gin): support http3 using quic-go/quic-go (#3210) * experimental support http3 * remove go1.14 and go1.15 * update quic-go package path * only support go1.19+ * remove go19 support * update gomod * chore: refine CI configuration and dependencies - Remove dynamic Go versioning in favor of pinning to major version `1` - Update linter version from `v1.56.2` to `v1.58.1` in GitHub Actions workflow Signed-off-by: Bo-Yi Wu * chore: refactor CI workflow and improve tests - Update the golangci-lint-action version from `v5` to `v6` in the GitHub workflow configuration Signed-off-by: Bo-Yi Wu * chore: update dependencies and CI configurations - Update Go version requirement from `1.20` to `1.21` in `go.mod` Signed-off-by: Bo-Yi Wu * style: refactor codebase and update tests - Add an empty line in the import section of `gin.go` Signed-off-by: Bo-Yi Wu * chore: enhance code quality and consistency - Add `gin.go` to the list of files with specific linters in `.golangci.yml`, applying the `gci` linter. Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu Co-authored-by: Bo-Yi Wu --- .github/workflows/gin.yml | 7 ++--- .golangci.yml | 3 ++ gin.go | 52 +++++++++++++++++++++++------------ gin_integration_test.go | 16 +++++++++++ go.mod | 18 +++++++++--- go.sum | 58 ++++++++++++++++++++++++++++++++------- 6 files changed, 119 insertions(+), 35 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 4a9dbc9..6121077 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -22,12 +22,11 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: "go.mod" - check-latest: true + go-version: "^1" - name: Setup golangci-lint - uses: golangci/golangci-lint-action@v5 + uses: golangci/golangci-lint-action@v6 with: - version: v1.56.2 + version: v1.58.1 args: --verbose test: needs: lint diff --git a/.golangci.yml b/.golangci.yml index 4a72f73..5a65972 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -55,3 +55,6 @@ issues: - linters: - revive path: _test\.go + - path: gin.go + linters: + - gci diff --git a/gin.go b/gin.go index 03edbff..57f8c2a 100644 --- a/gin.go +++ b/gin.go @@ -17,6 +17,8 @@ import ( "github.com/gin-gonic/gin/internal/bytesconv" "github.com/gin-gonic/gin/render" + + "github.com/quic-go/quic-go/http3" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) @@ -383,23 +385,6 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { return routes } -// Run attaches the router to a http.Server and starts listening and serving HTTP requests. -// It is a shortcut for http.ListenAndServe(addr, router) -// Note: this method will block the calling goroutine indefinitely unless an error happens. -func (engine *Engine) Run(addr ...string) (err error) { - defer func() { debugPrintError(err) }() - - if engine.isUnsafeTrustedProxies() { - debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") - } - - address := resolveAddress(addr) - debugPrint("Listening and serving HTTP on %s\n", address) - err = http.ListenAndServe(address, engine.Handler()) - return -} - func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) { if engine.trustedProxies == nil { return nil, nil @@ -503,6 +488,23 @@ func parseIP(ip string) net.IP { return parsedIP } +// Run attaches the router to a http.Server and starts listening and serving HTTP requests. +// It is a shortcut for http.ListenAndServe(addr, router) +// Note: this method will block the calling goroutine indefinitely unless an error happens. +func (engine *Engine) Run(addr ...string) (err error) { + defer func() { debugPrintError(err) }() + + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") + } + + address := resolveAddress(addr) + debugPrint("Listening and serving HTTP on %s\n", address) + err = http.ListenAndServe(address, engine.Handler()) + return +} + // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. @@ -564,6 +566,22 @@ func (engine *Engine) RunFd(fd int) (err error) { return } +// RunQUIC attaches the router to a http.Server and starts listening and serving QUIC requests. +// It is a shortcut for http3.ListenAndServeQUIC(addr, certFile, keyFile, router) +// Note: this method will block the calling goroutine indefinitely unless an error happens. +func (engine *Engine) RunQUIC(addr, certFile, keyFile string) (err error) { + debugPrint("Listening and serving QUIC on %s\n", addr) + defer func() { debugPrintError(err) }() + + if engine.isUnsafeTrustedProxies() { + debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + } + + err = http3.ListenAndServeQUIC(addr, certFile, keyFile, engine.Handler()) + return +} + // RunListener attaches the router to a http.Server and starts listening and serving HTTP requests // through the specified net.Listener func (engine *Engine) RunListener(listener net.Listener) (err error) { diff --git a/gin_integration_test.go b/gin_integration_test.go index 02b9622..2125df9 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -274,6 +274,22 @@ func TestBadUnixSocket(t *testing.T) { assert.Error(t, router.RunUnix("#/tmp/unix_unit_test")) } +func TestRunQUIC(t *testing.T) { + router := New() + go func() { + router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) + + assert.NoError(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + }() + + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + assert.Error(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + testRequest(t, "https://localhost:8443/example") +} + func TestFileDescriptor(t *testing.T) { router := New() diff --git a/go.mod b/go.mod index 3e94e50..6063f3b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gin-gonic/gin -go 1.20 +go 1.21 require ( github.com/bytedance/sonic v1.11.6 @@ -10,6 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.2 + github.com/quic-go/quic-go v0.43.1 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.25.0 @@ -25,14 +26,23 @@ require ( github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/quic-go/qpack v0.4.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - golang.org/x/arch v0.8.0 // indirect + go.uber.org/mock v0.4.0 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/mod v0.11.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.9.1 // indirect ) diff --git a/go.sum b/go.sum index ce905e7..44af4cc 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,14 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,41 +17,66 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ= +github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -59,24 +88,33 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 7e298066baab19316aa2ffc946f1bbc44a68a607 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 9 May 2024 13:45:03 +0800 Subject: [PATCH 866/912] build: update Gin minimum Go version to 1.21 (#3960) * build: update Gin minimum Go version to 1.21 - Update the minimum Go version requirement for Gin from `1.20` to `1.21` in both `debug.go` and `debug_test.go` - Modify the warning message to reflect the new minimum Go version requirement in `debug.go` - Adjust the test assertion to match the updated warning message in `debug_test.go` Signed-off-by: Bo-Yi Wu * docs: refine project documentation and CI configurations - Update supported Go versions for GitHub actions to `1.21` and `1.22` - Specify the required Go version as `1.21` or above in README - Change code block syntax to `sh` in installation and demo run instructions - Remove empty lines in README sections - Update project list formatting without changing the content Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 2 +- README.md | 25 ++++++++++--------------- debug.go | 4 ++-- debug_test.go | 2 +- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 6121077..947abf9 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.20", "1.21", "1.22"] + go: ["1.21", "1.22"] test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: diff --git a/README.md b/README.md index e007bf2..04217d6 100644 --- a/README.md +++ b/README.md @@ -25,18 +25,17 @@ Gin is a web framework written in [Go](https://go.dev/). It features a martini-l - Rendering built-in - Extendable - ## Getting started ### Prerequisites -- **[Go](https://go.dev/)**: any one of the **three latest major** [releases](https://go.dev/doc/devel/release) (we test it with these). +The required version of [Go](https://go.dev/) language is [1.21](https://go.dev/doc/devel/release#go1.21.0) or above. ### Getting Gin With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import -``` +```sh import "github.com/gin-gonic/gin" ``` @@ -45,7 +44,7 @@ to your code, and then `go [build|run|test]` will automatically fetch the necess Otherwise, run the following Go command to install the `gin` package: ```sh -$ go get -u github.com/gin-gonic/gin +go get -u github.com/gin-gonic/gin ``` ### Running Gin @@ -74,7 +73,7 @@ func main() { And use the Go command to run the demo: -``` +```sh # run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go ``` @@ -89,7 +88,6 @@ Learn and practice more examples, please read the [Gin Quick Start](docs/doc.md) A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. - ## Documentation See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. @@ -153,23 +151,20 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better - ## Middlewares You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). - ## Users Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. -* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. -* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. -* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. -* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. -* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. - +- [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. +- [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. +- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. +- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. +- [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. +- [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. ## Contributing diff --git a/debug.go b/debug.go index ae346e9..62085c5 100644 --- a/debug.go +++ b/debug.go @@ -13,7 +13,7 @@ import ( "sync/atomic" ) -const ginSupportMinGoVer = 18 +const ginSupportMinGoVer = 21 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -78,7 +78,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.20+. + debugPrint(`[WARNING] Now Gin requires Go 1.21+. `) } diff --git a/debug_test.go b/debug_test.go index e390972..1e57668 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.20+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.21+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } From c677ccc40a60386565dd0d755efacb85d153feca Mon Sep 17 00:00:00 2001 From: thinkerou Date: Fri, 10 May 2024 07:27:42 +0800 Subject: [PATCH 867/912] fix(go): invalid Go toolchain version (#3961) --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6063f3b..4937d2b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gin-gonic/gin -go 1.21 +go 1.21.0 require ( github.com/bytedance/sonic v1.11.6 From 40131af1243ef90e026859bf8ff9c30a5a230351 Mon Sep 17 00:00:00 2001 From: Mobin Mohanan <47410557+tr1sm0s1n@users.noreply.github.com> Date: Mon, 13 May 2024 06:59:21 +0530 Subject: [PATCH 868/912] ci(Makefile): added help and descriptions to targets (#3964) --- Makefile | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index b58f24f..1a7de86 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | gr TESTTAGS ?= "" .PHONY: test +# Run tests to verify code functionality. test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ @@ -30,10 +31,12 @@ test: done .PHONY: fmt +# Ensure consistent code formatting. fmt: $(GOFMT) -w $(GOFILES) .PHONY: fmt-check +# format (check only). fmt-check: @diff=$$($(GOFMT) -d $(GOFILES)); \ if [ -n "$$diff" ]; then \ @@ -43,31 +46,36 @@ fmt-check: fi; .PHONY: vet +# Examine packages and report suspicious constructs if any. vet: $(GO) vet $(VETPACKAGES) .PHONY: lint +# Inspect source code for stylistic errors or potential bugs. lint: @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u golang.org/x/lint/golint; \ fi for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; -.PHONY: misspell-check -misspell-check: +.PHONY: misspell +# Correct commonly misspelled English words in source code. +misspell: @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi - misspell -error $(GOFILES) + misspell -w $(GOFILES) -.PHONY: misspell -misspell: +.PHONY: misspell-check +# misspell (check only). +misspell-check: @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u github.com/client9/misspell/cmd/misspell; \ fi - misspell -w $(GOFILES) + misspell -error $(GOFILES) .PHONY: tools +# Install tools (golint and misspell). tools: @if [ $(GO_VERSION) -gt 15 ]; then \ $(GO) install golang.org/x/lint/golint@latest; \ @@ -76,3 +84,23 @@ tools: $(GO) install golang.org/x/lint/golint; \ $(GO) install github.com/client9/misspell/cmd/misspell; \ fi + +.PHONY: help +# Help. +help: + @echo '' + @echo 'Usage:' + @echo ' make [target]' + @echo '' + @echo 'Targets:' + @awk '/^[a-zA-Z\-\0-9]+:/ { \ + helpMessage = match(lastLine, /^# (.*)/); \ + if (helpMessage) { \ + helpCommand = substr($$1, 0, index($$1, ":")-1); \ + helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \ + printf " - \033[36m%-20s\033[0m %s\n", helpCommand, helpMessage; \ + } \ + } \ + { lastLine = $$0 }' $(MAKEFILE_LIST) + +.DEFAULT_GOAL := help From 6ca8ddb1aed78d9ffaf984e5489111838242fedb Mon Sep 17 00:00:00 2001 From: guonaihong Date: Mon, 13 May 2024 11:11:56 +0800 Subject: [PATCH 869/912] feat(binding): add BindPlain (#3904) * add BindPlain * fix ci/cd error --- binding/binding.go | 1 + binding/binding_nomsgpack.go | 1 + binding/binding_test.go | 40 +++++++++++ binding/plain.go | 56 ++++++++++++++++ context.go | 17 ++++- context_test.go | 124 +++++++++++++++++++++++++++++++++++ 6 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 binding/plain.go diff --git a/binding/binding.go b/binding/binding.go index 9472387..702d0e8 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -84,6 +84,7 @@ var ( YAML BindingBody = yamlBinding{} Uri BindingUri = uriBinding{} Header Binding = headerBinding{} + Plain BindingBody = plainBinding{} TOML BindingBody = tomlBinding{} ) diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go index 552a86b..c8e6131 100644 --- a/binding/binding_nomsgpack.go +++ b/binding/binding_nomsgpack.go @@ -81,6 +81,7 @@ var ( Uri = uriBinding{} Header = headerBinding{} TOML = tomlBinding{} + Plain = plainBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method diff --git a/binding/binding_test.go b/binding/binding_test.go index feb8eed..c59e5e9 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1342,6 +1342,46 @@ func (h hook) Read([]byte) (int, error) { return 0, errors.New("error") } +type failRead struct{} + +func (f *failRead) Read(b []byte) (n int, err error) { + return 0, errors.New("my fail") +} + +func (f *failRead) Close() error { + return nil +} + +func TestPlainBinding(t *testing.T) { + p := Plain + assert.Equal(t, "plain", p.Name()) + + var s string + req := requestWithBody("POST", "/", "test string") + assert.NoError(t, p.Bind(req, &s)) + assert.Equal(t, s, "test string") + + var bs []byte + req = requestWithBody("POST", "/", "test []byte") + assert.NoError(t, p.Bind(req, &bs)) + assert.Equal(t, bs, []byte("test []byte")) + + var i int + req = requestWithBody("POST", "/", "test fail") + assert.Error(t, p.Bind(req, &i)) + + req = requestWithBody("POST", "/", "") + req.Body = &failRead{} + assert.Error(t, p.Bind(req, &s)) + + req = requestWithBody("POST", "/", "") + assert.Nil(t, p.Bind(req, nil)) + + var ptr *string + req = requestWithBody("POST", "/", "") + assert.Nil(t, p.Bind(req, ptr)) +} + func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) diff --git a/binding/plain.go b/binding/plain.go new file mode 100644 index 0000000..3b250bb --- /dev/null +++ b/binding/plain.go @@ -0,0 +1,56 @@ +package binding + +import ( + "fmt" + "io" + "net/http" + "reflect" + + "github.com/gin-gonic/gin/internal/bytesconv" +) + +type plainBinding struct{} + +func (plainBinding) Name() string { + return "plain" +} + +func (plainBinding) Bind(req *http.Request, obj interface{}) error { + all, err := io.ReadAll(req.Body) + if err != nil { + return err + } + + return decodePlain(all, obj) +} + +func (plainBinding) BindBody(body []byte, obj any) error { + return decodePlain(body, obj) +} + +func decodePlain(data []byte, obj any) error { + if obj == nil { + return nil + } + + v := reflect.ValueOf(obj) + + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return nil + } + v = v.Elem() + } + + if v.Kind() == reflect.String { + v.SetString(bytesconv.BytesToString(data)) + return nil + } + + if _, ok := v.Interface().([]byte); ok { + v.SetBytes(data) + return nil + } + + return fmt.Errorf("type (%T) unknown type", v) +} diff --git a/context.go b/context.go index 391adaf..3525066 100644 --- a/context.go +++ b/context.go @@ -614,7 +614,7 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error } defer src.Close() - if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil { + if err = os.MkdirAll(filepath.Dir(dst), 0o750); err != nil { return err } @@ -667,6 +667,11 @@ func (c *Context) BindTOML(obj any) error { return c.MustBindWith(obj, binding.TOML) } +// BindPlain is a shortcut for c.MustBindWith(obj, binding.Plain). +func (c *Context) BindPlain(obj any) error { + return c.MustBindWith(obj, binding.Plain) +} + // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). func (c *Context) BindHeader(obj any) error { return c.MustBindWith(obj, binding.Header) @@ -732,6 +737,11 @@ func (c *Context) ShouldBindTOML(obj any) error { return c.ShouldBindWith(obj, binding.TOML) } +// ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain). +func (c *Context) ShouldBindPlain(obj any) error { + return c.ShouldBindWith(obj, binding.Plain) +} + // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). func (c *Context) ShouldBindHeader(obj any) error { return c.ShouldBindWith(obj, binding.Header) @@ -794,6 +804,11 @@ func (c *Context) ShouldBindBodyWithTOML(obj any) error { return c.ShouldBindBodyWith(obj, binding.TOML) } +// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). +func (c *Context) ShouldBindBodyWithPlain(obj any) error { + return c.ShouldBindBodyWith(obj, binding.Plain) +} + // ClientIP implements one best effort algorithm to return the real client IP. // It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. // If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). diff --git a/context_test.go b/context_test.go index ae34c65..36d6e34 100644 --- a/context_test.go +++ b/context_test.go @@ -1670,6 +1670,31 @@ func TestContextBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextBindPlain(t *testing.T) { + + // string + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`)) + c.Request.Header.Add("Content-Type", MIMEPlain) + + var s string + + assert.NoError(t, c.BindPlain(&s)) + assert.Equal(t, "test string", s) + assert.Equal(t, 0, w.Body.Len()) + + // []byte + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`)) + c.Request.Header.Add("Content-Type", MIMEPlain) + + var bs []byte + + assert.NoError(t, c.BindPlain(&bs)) + assert.Equal(t, []byte("test []byte"), bs) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -1816,6 +1841,31 @@ func TestContextShouldBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextShouldBindPlain(t *testing.T) { + // string + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`)) + c.Request.Header.Add("Content-Type", MIMEPlain) + + var s string + + assert.NoError(t, c.ShouldBindPlain(&s)) + assert.Equal(t, "test string", s) + assert.Equal(t, 0, w.Body.Len()) + // []byte + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`)) + c.Request.Header.Add("Content-Type", MIMEPlain) + + var bs []byte + + assert.NoError(t, c.ShouldBindPlain(&bs)) + assert.Equal(t, []byte("test []byte"), bs) + assert.Equal(t, 0, w.Body.Len()) + +} + func TestContextShouldBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -2247,6 +2297,80 @@ func TestContextShouldBindBodyWithTOML(t *testing.T) { } } +func TestContextShouldBindBodyWithPlain(t *testing.T) { + for _, tt := range []struct { + name string + bindingBody binding.BindingBody + body string + }{ + { + name: " JSON & JSON-BODY ", + bindingBody: binding.JSON, + body: `{"foo":"FOO"}`, + }, + { + name: " JSON & XML-BODY ", + bindingBody: binding.XML, + body: ` + +FOO +`, + }, + { + name: " JSON & YAML-BODY ", + bindingBody: binding.YAML, + body: `foo: FOO`, + }, + { + name: " JSON & TOM-BODY ", + bindingBody: binding.TOML, + body: `foo=FOO`, + }, + { + name: " JSON & Plain-BODY ", + bindingBody: binding.Plain, + body: `foo=FOO`, + }, + } { + t.Logf("testing: %s", tt.name) + + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + + type typeJSON struct { + Foo string `json:"foo" binding:"required"` + } + objJSON := typeJSON{} + + if tt.bindingBody == binding.Plain { + body := "" + assert.NoError(t, c.ShouldBindBodyWithPlain(&body)) + assert.Equal(t, body, "foo=FOO") + } + + if tt.bindingBody == binding.JSON { + assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{"FOO"}, objJSON) + } + + if tt.bindingBody == binding.XML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + + if tt.bindingBody == binding.YAML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + + if tt.bindingBody == binding.TOML { + assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + assert.Equal(t, typeJSON{}, objJSON) + } + } +} func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) From a569ed8f26a13b10c02920de103eba79c7102cd7 Mon Sep 17 00:00:00 2001 From: crunchyfrog <49813441+truecrunchyfrog@users.noreply.github.com> Date: Mon, 13 May 2024 05:12:55 +0200 Subject: [PATCH 870/912] docs(readme): fix language and moved link (#3962) * Update README.md * more fixes & fix moved link --- README.md | 62 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 04217d6..a595656 100644 --- a/README.md +++ b/README.md @@ -11,37 +11,36 @@ [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) [![TODOs](https://badgen.net/https/api.tickgit.com/badgen/github.com/gin-gonic/gin)](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin) -Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. +Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). +If you need performance and good productivity, you will love Gin. -**The key features of Gin are:** +**Gin's key features are:** - Zero allocation router -- Fast +- Speed - Middleware support - Crash-free - JSON validation -- Routes grouping +- Route grouping - Error management -- Rendering built-in -- Extendable +- Built-in rendering +- Extensible ## Getting started ### Prerequisites -The required version of [Go](https://go.dev/) language is [1.21](https://go.dev/doc/devel/release#go1.21.0) or above. +Gin requires [Go](https://go.dev/) version [1.21](https://go.dev/doc/devel/release#go1.21.0) or above. ### Getting Gin -With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import +With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), `go [build|run|test]` automatically fetches the necessary dependencies when you add the import in your code: ```sh import "github.com/gin-gonic/gin" ``` -to your code, and then `go [build|run|test]` will automatically fetch the necessary dependencies. - -Otherwise, run the following Go command to install the `gin` package: +Alternatively, use `go get`: ```sh go get -u github.com/gin-gonic/gin @@ -49,7 +48,7 @@ go get -u github.com/gin-gonic/gin ### Running Gin -First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`: +A basic example: ```go package main @@ -71,28 +70,29 @@ func main() { } ``` -And use the Go command to run the demo: +To run the code, use the `go run` command, like: ```sh -# run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go ``` -### Learn more examples +Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response! + +### See more examples #### Quick Start -Learn and practice more examples, please read the [Gin Quick Start](docs/doc.md) which includes API examples and builds tag. +Learn and practice with the [Gin Quick Start](docs/doc.md), which includes API examples and builds tag. #### Examples -A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. +A number of ready-to-run examples demonstrating various use cases of Gin are available in the [Gin examples](https://github.com/gin-gonic/examples) repository. ## Documentation -See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. +See the [API documentation on godoc.org](https://godoc.org/github.com/gin-gonic/gin). -All documentation is available on the Gin website. +The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages: - [English](https://gin-gonic.com/docs/) - [简体中文](https://gin-gonic.com/zh-cn/docs/) @@ -103,15 +103,13 @@ All documentation is available on the Gin website. - [Turkish](https://gin-gonic.com/tr/docs/) - [Persian](https://gin-gonic.com/fa/docs/) -### Articles about Gin - -A curated list of awesome Gin framework. +### Articles - [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin) ## Benchmarks -Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks details](/BENCHMARKS.md). +Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks](/BENCHMARKS.md). | Benchmark name | (1) | (2) | (3) | (4) | | ------------------------------ | ---------:| ---------------:| ------------:| ---------------:| @@ -151,23 +149,23 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better -## Middlewares +## Middleware You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). -## Users +## Uses -Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. +Here are some awesome projects that are using the [Gin](https://github.com/gin-gonic/gin) web framework. -- [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. -- [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. -- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares. -- [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. +- [gorush](https://github.com/appleboy/gorush): A push notification server. +- [fnproject](https://github.com/fnproject/fn): A container native, cloud agnostic serverless platform. +- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Google TensorFlow. +- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middleware. +- [picfit](https://github.com/thoas/picfit): An image resizing server. - [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. ## Contributing Gin is the work of hundreds of contributors. We appreciate your help! -Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. +Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. From 3f5b0afa2ac85ea79638ca08f4140ce64b8246e5 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Mon, 13 May 2024 13:32:46 +0800 Subject: [PATCH 871/912] refactor(slice): simplify SliceValidationError Error method (#3910) * Simplify SliceValidationError Error method * Replace fmt.Fprintf with b.WriteString --------- Co-authored-by: huangzw Co-authored-by: 1911860538 --- binding/default_validator.go | 27 +++++++++------------ binding/default_validator_benchmark_test.go | 12 ++++++--- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/binding/default_validator.go b/binding/default_validator.go index ac43d7c..44b7a2a 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -5,8 +5,8 @@ package binding import ( - "fmt" "reflect" + "strconv" "strings" "sync" @@ -22,25 +22,20 @@ type SliceValidationError []error // Error concatenates all error elements in SliceValidationError into a single string separated by \n. func (err SliceValidationError) Error() string { - n := len(err) - switch n { - case 0: + if len(err) == 0 { return "" - default: - var b strings.Builder - if err[0] != nil { - fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error()) - } - if n > 1 { - for i := 1; i < n; i++ { - if err[i] != nil { - b.WriteString("\n") - fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error()) - } + } + + var b strings.Builder + for i := 0; i < len(err); i++ { + if err[i] != nil { + if b.Len() > 0 { + b.WriteString("\n") } + b.WriteString("[" + strconv.Itoa(i) + "]: " + err[i].Error()) } - return b.String() } + return b.String() } var _ StructValidator = (*defaultValidator)(nil) diff --git a/binding/default_validator_benchmark_test.go b/binding/default_validator_benchmark_test.go index 9292e2a..4454741 100644 --- a/binding/default_validator_benchmark_test.go +++ b/binding/default_validator_benchmark_test.go @@ -12,11 +12,15 @@ import ( func BenchmarkSliceValidationError(b *testing.B) { const size int = 100 + e := make(SliceValidationError, size) + for j := 0; j < size; j++ { + e[j] = errors.New(strconv.Itoa(j)) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { - e := make(SliceValidationError, size) - for j := 0; j < size; j++ { - e[j] = errors.New(strconv.Itoa(j)) - } if len(e.Error()) == 0 { b.Errorf("error") } From 36b0dede4b8c4a67d92c4107cebc5a068364321d Mon Sep 17 00:00:00 2001 From: 51pwn <18223385+hktalent@users.noreply.github.com> Date: Mon, 13 May 2024 14:55:41 +0800 Subject: [PATCH 872/912] fix(context): check handler is nil (#3413) * fixed #3404 2022-11-23 * up 2022-11-23 * refactor: refactor context handling and nil checks - Refactor nil checks to improve readability in `context.go` - Modify the control flow in `HandlerNames` and `Next` methods to continue on nil values before appending or invoking handlers in `context.go` Signed-off-by: Bo-Yi Wu * test: refactor context_test.go for clarity and efficiency - Insert a `nil` value into the `HandlersChain` array in `context_test.go` - Remove empty test functions in `context_test.go` Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu Co-authored-by: Bo-Yi Wu --- context.go | 6 ++++++ context_test.go | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 3525066..cd2645a 100644 --- a/context.go +++ b/context.go @@ -152,6 +152,9 @@ func (c *Context) HandlerName() string { func (c *Context) HandlerNames() []string { hn := make([]string, 0, len(c.handlers)) for _, val := range c.handlers { + if val == nil { + continue + } hn = append(hn, nameOfFunction(val)) } return hn @@ -182,6 +185,9 @@ func (c *Context) FullPath() string { func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { + if c.handlers[c.index] == nil { + continue + } c.handlers[c.index](c) c.index++ } diff --git a/context_test.go b/context_test.go index 36d6e34..517f73e 100644 --- a/context_test.go +++ b/context_test.go @@ -362,7 +362,7 @@ func TestContextHandlerName(t *testing.T) { func TestContextHandlerNames(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest, func(c *Context) {}, handlerNameTest2} + c.handlers = HandlersChain{func(c *Context) {}, nil, handlerNameTest, func(c *Context) {}, handlerNameTest2} names := c.HandlerNames() @@ -1671,7 +1671,6 @@ func TestContextBindWithXML(t *testing.T) { } func TestContextBindPlain(t *testing.T) { - // string w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -1863,7 +1862,6 @@ func TestContextShouldBindPlain(t *testing.T) { assert.NoError(t, c.ShouldBindPlain(&bs)) assert.Equal(t, []byte("test []byte"), bs) assert.Equal(t, 0, w.Body.Len()) - } func TestContextShouldBindHeader(t *testing.T) { @@ -2371,6 +2369,7 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { } } } + func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) From 4f339e6a35b163d31b30916b37f4176d385f41bd Mon Sep 17 00:00:00 2001 From: RedCrazyGhost <49381700+RedCrazyGhost@users.noreply.github.com> Date: Tue, 14 May 2024 10:25:54 +0800 Subject: [PATCH 873/912] fix(context): YAML judgment logic in Negotiate (#3966) --- context.go | 3 ++- context_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index cd2645a..d2d5497 100644 --- a/context.go +++ b/context.go @@ -34,6 +34,7 @@ const ( MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm MIMEYAML = binding.MIMEYAML + MIMEYAML2 = binding.MIMEYAML2 MIMETOML = binding.MIMETOML ) @@ -1182,7 +1183,7 @@ func (c *Context) Negotiate(code int, config Negotiate) { data := chooseData(config.XMLData, config.Data) c.XML(code, data) - case binding.MIMEYAML: + case binding.MIMEYAML, binding.MIMEYAML2: data := chooseData(config.YAMLData, config.Data) c.YAML(code, data) diff --git a/context_test.go b/context_test.go index 517f73e..b700389 100644 --- a/context_test.go +++ b/context_test.go @@ -1196,7 +1196,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEJSON, MIMEXML, MIMEYAML}, + Offered: []string{MIMEJSON, MIMEXML, MIMEYAML, MIMEYAML2}, Data: H{"foo": "bar"}, }) @@ -1211,7 +1211,7 @@ func TestContextNegotiationWithXML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEXML, MIMEJSON, MIMEYAML}, + Offered: []string{MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2}, Data: H{"foo": "bar"}, }) @@ -1226,7 +1226,7 @@ func TestContextNegotiationWithYAML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML}, + Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML, MIMEYAML2}, Data: H{"foo": "bar"}, }) @@ -1241,7 +1241,7 @@ func TestContextNegotiationWithTOML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML}, + Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2}, Data: H{"foo": "bar"}, }) From e0d46ded6cb6974d55a255ab122d1aa6ca0cd60e Mon Sep 17 00:00:00 2001 From: Adriano Sela Aviles Date: Sat, 18 May 2024 19:48:07 -0700 Subject: [PATCH 874/912] fix(context): verify URL is Non-nil in initQueryCache() (#3969) --- context.go | 2 +- context_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index d2d5497..baa4b0f 100644 --- a/context.go +++ b/context.go @@ -475,7 +475,7 @@ func (c *Context) QueryArray(key string) (values []string) { func (c *Context) initQueryCache() { if c.queryCache == nil { - if c.Request != nil { + if c.Request != nil && c.Request.URL != nil { c.queryCache = c.Request.URL.Query() } else { c.queryCache = url.Values{} diff --git a/context_test.go b/context_test.go index b700389..8bbf270 100644 --- a/context_test.go +++ b/context_test.go @@ -423,6 +423,49 @@ func TestContextQuery(t *testing.T) { assert.Empty(t, c.PostForm("foo")) } +func TestContextInitQueryCache(t *testing.T) { + validURL, err := url.Parse("https://github.com/gin-gonic/gin/pull/3969?key=value&otherkey=othervalue") + assert.Nil(t, err) + + tests := []struct { + testName string + testContext *Context + expectedQueryCache url.Values + }{ + { + testName: "queryCache should remain unchanged if already not nil", + testContext: &Context{ + queryCache: url.Values{"a": []string{"b"}}, + Request: &http.Request{URL: validURL}, // valid request for evidence that values weren't extracted + }, + expectedQueryCache: url.Values{"a": []string{"b"}}, + }, + { + testName: "queryCache should be empty when Request is nil", + testContext: &Context{Request: nil}, // explicit nil for readability + expectedQueryCache: url.Values{}, + }, + { + testName: "queryCache should be empty when Request.URL is nil", + testContext: &Context{Request: &http.Request{URL: nil}}, // explicit nil for readability + expectedQueryCache: url.Values{}, + }, + { + testName: "queryCache should be populated when it not yet populated and Request + Request.URL are non nil", + testContext: &Context{Request: &http.Request{URL: validURL}}, // explicit nil for readability + expectedQueryCache: url.Values{"key": []string{"value"}, "otherkey": []string{"othervalue"}}, + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + test.testContext.initQueryCache() + assert.Equal(t, test.expectedQueryCache, test.testContext.queryCache) + }) + } + +} + func TestContextDefaultQueryOnEmptyRequest(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // here c.Request == nil assert.NotPanics(t, func() { From 24d67647cb9b4e0bbdcdec7f0c2086e8004e1572 Mon Sep 17 00:00:00 2001 From: bruceNu1l <144002160+bruceNu1l@users.noreply.github.com> Date: Thu, 23 May 2024 10:16:11 +0800 Subject: [PATCH 875/912] feat(form): add custom string slice for form tag unmarshal (#3970) (#3971) Co-authored-by: Bruce Lee --- binding/form_mapping.go | 11 +++++ binding/form_mapping_test.go | 87 ++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 108606f..33389b2 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -193,14 +193,25 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ if !ok { vs = []string{opt.defaultValue} } + + if ok, err = trySetCustom(vs[0], value); ok { + return ok, err + } + return true, setSlice(vs, value, field) case reflect.Array: if !ok { vs = []string{opt.defaultValue} } + + if ok, err = trySetCustom(vs[0], value); ok { + return ok, err + } + if len(vs) != value.Len() { return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) } + return true, setArray(vs, value, field) default: var val string diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index ed01a08..afd51f9 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -5,6 +5,7 @@ package binding import ( + "encoding/hex" "fmt" "mime/multipart" "reflect" @@ -422,3 +423,89 @@ func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { assert.EqualValues(t, "/foo", s.FileData.Path) assert.EqualValues(t, "happiness", s.FileData.Name) } + +type customPath []string + +func (p *customPath) UnmarshalParam(param string) error { + elems := strings.Split(param, "/") + n := len(elems) + if n < 2 { + return fmt.Errorf("invalid format") + } + + *p = elems + return nil +} + +func TestMappingCustomSliceUri(t *testing.T) { + var s struct { + FileData customPath `uri:"path"` + } + err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri") + assert.NoError(t, err) + + assert.EqualValues(t, "bar", s.FileData[0]) + assert.EqualValues(t, "foo", s.FileData[1]) +} + +func TestMappingCustomSliceForm(t *testing.T) { + var s struct { + FileData customPath `form:"path"` + } + err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form") + assert.NoError(t, err) + + assert.EqualValues(t, "bar", s.FileData[0]) + assert.EqualValues(t, "foo", s.FileData[1]) +} + +type objectID [12]byte + +func (o *objectID) UnmarshalParam(param string) error { + oid, err := convertTo(param) + if err != nil { + return err + } + + *o = oid + return nil +} + +func convertTo(s string) (objectID, error) { + var nilObjectID objectID + if len(s) != 24 { + return nilObjectID, fmt.Errorf("invalid format") + } + + var oid [12]byte + _, err := hex.Decode(oid[:], []byte(s)) + if err != nil { + return nilObjectID, err + } + + return oid, nil +} + +func TestMappingCustomArrayUri(t *testing.T) { + var s struct { + FileData objectID `uri:"id"` + } + val := `664a062ac74a8ad104e0e80f` + err := mappingByPtr(&s, formSource{"id": {val}}, "uri") + assert.NoError(t, err) + + expected, _ := convertTo(val) + assert.EqualValues(t, expected, s.FileData) +} + +func TestMappingCustomArrayForm(t *testing.T) { + var s struct { + FileData objectID `form:"id"` + } + val := `664a062ac74a8ad104e0e80f` + err := mappingByPtr(&s, formSource{"id": {val}}, "form") + assert.NoError(t, err) + + expected, _ := convertTo(val) + assert.EqualValues(t, expected, s.FileData) +} From 334160bab772f6f93767b870f9d07c176cd4aa2b Mon Sep 17 00:00:00 2001 From: Endless Paradox Date: Fri, 24 May 2024 14:55:25 +0800 Subject: [PATCH 876/912] chore(tree): replace the self-defined 'min' to official one (#3975) --- tree.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/tree.go b/tree.go index 878023d..ce0f065 100644 --- a/tree.go +++ b/tree.go @@ -65,17 +65,10 @@ func (trees methodTrees) get(method string) *node { return nil } -func min(a, b int) int { - if a <= b { - return a - } - return b -} - func longestCommonPrefix(a, b string) int { i := 0 - max := min(len(a), len(b)) - for i < max && a[i] == b[i] { + max_ := min(len(a), len(b)) + for i < max_ && a[i] == b[i] { i++ } return i @@ -205,7 +198,7 @@ walk: } // Check if a child with the next path byte exists - for i, max := 0, len(n.indices); i < max; i++ { + for i, max_ := 0, len(n.indices); i < max_; i++ { if c == n.indices[i] { parentFullPathIndex += len(n.path) i = n.incrementChildPrio(i) @@ -770,7 +763,7 @@ walk: // Outer loop for walking the tree // Runes are up to 4 byte long, // -4 would definitely be another rune. var off int - for max := min(npLen, 3); off < max; off++ { + for max_ := min(npLen, 3); off < max_; off++ { if i := npLen - off; utf8.RuneStart(oldPath[i]) { // read rune from cached path rv, _ = utf8.DecodeRuneInString(oldPath[i:]) From 4621b7ac982335d9a74432e182dd2bfc6d841431 Mon Sep 17 00:00:00 2001 From: wssccc Date: Sat, 1 Jun 2024 13:44:57 +0800 Subject: [PATCH 877/912] feat(router): add literal colon support (#1432) (#2857) --- gin.go | 25 ++++++++++++++++++++++++- gin_integration_test.go | 25 +++++++++++++++++++++++++ tree.go | 12 ++++++++++++ tree_test.go | 22 ++++++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 57f8c2a..5ba1cf6 100644 --- a/gin.go +++ b/gin.go @@ -24,6 +24,9 @@ import ( ) const defaultMultipartMemory = 32 << 20 // 32 MB +const escapedColon = "\\:" +const colon = ":" +const backslash = "\\" var ( default404Body = []byte("404 page not found") @@ -474,6 +477,26 @@ func (engine *Engine) validateHeader(header string) (clientIP string, valid bool return "", false } +// updateRouteTree do update to the route tree recursively +func updateRouteTree(n *node) { + n.path = strings.ReplaceAll(n.path, escapedColon, colon) + n.fullPath = strings.ReplaceAll(n.fullPath, escapedColon, colon) + n.indices = strings.ReplaceAll(n.indices, backslash, colon) + if n.children == nil { + return + } + for _, child := range n.children { + updateRouteTree(child) + } +} + +// updateRouteTrees do update to the route trees +func (engine *Engine) updateRouteTrees() { + for _, tree := range engine.trees { + updateRouteTree(tree.root) + } +} + // parseIP parse a string representation of an IP and returns a net.IP with the // minimum byte representation or nil if input is invalid. func parseIP(ip string) net.IP { @@ -498,7 +521,7 @@ func (engine *Engine) Run(addr ...string) (err error) { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } - + engine.updateRouteTrees() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine.Handler()) diff --git a/gin_integration_test.go b/gin_integration_test.go index 2125df9..5398271 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -577,3 +577,28 @@ func TestTreeRunDynamicRouting(t *testing.T) { func isWindows() bool { return runtime.GOOS == "windows" } + +func TestEscapedColon(t *testing.T) { + router := New() + f := func(u string) { + router.GET(u, func(c *Context) { c.String(http.StatusOK, u) }) + } + f("/r/r\\:r") + f("/r/r:r") + f("/r/r/:r") + f("/r/r/\\:r") + f("/r/r/r\\:r") + assert.Panics(t, func() { + f("\\foo:") + }) + + router.updateRouteTrees() + ts := httptest.NewServer(router) + defer ts.Close() + + testRequest(t, ts.URL+"/r/r123", "", "/r/r:r") + testRequest(t, ts.URL+"/r/r:r", "", "/r/r\\:r") + testRequest(t, ts.URL+"/r/r/r123", "", "/r/r/:r") + testRequest(t, ts.URL+"/r/r/:r", "", "/r/r/\\:r") + testRequest(t, ts.URL+"/r/r/r:r", "", "/r/r/r\\:r") +} diff --git a/tree.go b/tree.go index ce0f065..b0a5f98 100644 --- a/tree.go +++ b/tree.go @@ -262,7 +262,19 @@ walk: // Returns -1 as index, if no wildcard was found. func findWildcard(path string) (wildcard string, i int, valid bool) { // Find start + escapeColon := false for start, c := range []byte(path) { + if escapeColon { + escapeColon = false + if c == ':' { + continue + } + panic("invalid escape string in path '" + path + "'") + } + if c == '\\' { + escapeColon = true + continue + } // A wildcard starts with ':' (param) or '*' (catch-all) if c != ':' && c != '*' { continue diff --git a/tree_test.go b/tree_test.go index c9b0313..3aa3a59 100644 --- a/tree_test.go +++ b/tree_test.go @@ -192,6 +192,7 @@ func TestTreeWildcard(t *testing.T) { "/get/abc/123abg/:param", "/get/abc/123abf/:param", "/get/abc/123abfff/:param", + "/get/abc/escaped_colon/test\\:param", } for _, route := range routes { tree.addRoute(route, fakeHandler(route)) @@ -315,6 +316,7 @@ func TestTreeWildcard(t *testing.T) { {"/get/abc/123abg/test", false, "/get/abc/123abg/:param", Params{Param{Key: "param", Value: "test"}}}, {"/get/abc/123abf/testss", false, "/get/abc/123abf/:param", Params{Param{Key: "param", Value: "testss"}}}, {"/get/abc/123abfff/te", false, "/get/abc/123abfff/:param", Params{Param{Key: "param", Value: "te"}}}, + {"/get/abc/escaped_colon/test\\:param", false, "/get/abc/escaped_colon/test\\:param", nil}, }) checkPriorities(t, tree) @@ -419,6 +421,9 @@ func TestTreeWildcardConflict(t *testing.T) { {"/id/:id", false}, {"/static/*file", false}, {"/static/", true}, + {"/escape/test\\:d1", false}, + {"/escape/test\\:d2", false}, + {"/escape/test:param", false}, } testRoutes(t, routes) } @@ -971,3 +976,20 @@ func TestTreeWildcardConflictEx(t *testing.T) { } } } + +func TestTreeInvalidEscape(t *testing.T) { + routes := map[string]bool{ + "/r1/r": true, + "/r2/:r": true, + "/r3/\\:r": true, + } + tree := &node{} + for route, valid := range routes { + recv := catchPanic(func() { + tree.addRoute(route, fakeHandler(route)) + }) + if recv == nil != valid { + t.Fatalf("%s should be %t but got %v", route, valid, recv) + } + } +} From 64ead9e6bd924d431f4dd612349bc5e13300e6fc Mon Sep 17 00:00:00 2001 From: Meng Zhuo Date: Thu, 6 Jun 2024 17:10:03 +0800 Subject: [PATCH 878/912] docs(readme): replace godoc with pkg (#3985) * Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a595656..faeb495 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Build Status](https://github.com/gin-gonic/gin/workflows/Run%20Tests/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster) [![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin) [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) -[![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) +[![Go Reference](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc) [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases) @@ -90,7 +90,7 @@ A number of ready-to-run examples demonstrating various use cases of Gin are ava ## Documentation -See the [API documentation on godoc.org](https://godoc.org/github.com/gin-gonic/gin). +See the [API documentation on go.dev](https://pkg.go.dev/github.com/gin-gonic/gin). The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages: From 9c081de9cdd1948f521d47d170d18cbc2981c33a Mon Sep 17 00:00:00 2001 From: demouth <1133178+demouth@users.noreply.github.com> Date: Sun, 16 Jun 2024 01:28:08 +0900 Subject: [PATCH 879/912] docs: fix typo in Gin Quick Start (#3997) --- docs/doc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doc.md b/docs/doc.md index 177c447..5136640 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -524,7 +524,7 @@ func main() { return c.Writer.Status() < http.StatusInternalServerError } - engine.Use(gin.LoggerWithConfig(loggerConfig)) + router.Use(gin.LoggerWithConfig(loggerConfig)) router.Use(gin.Recovery()) // skipped From 626d55b0c02937645c21774cacc021713de88604 Mon Sep 17 00:00:00 2001 From: Pierre-Henri Symoneaux Date: Sat, 22 Jun 2024 16:19:04 +0200 Subject: [PATCH 880/912] fix(gin): Do not panic when handling method not allowed on empty tree (#4003) Signed-off-by: Pierre-Henri Symoneaux --- gin.go | 2 +- gin_test.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 5ba1cf6..48cc15c 100644 --- a/gin.go +++ b/gin.go @@ -687,7 +687,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { break } - if engine.HandleMethodNotAllowed { + if engine.HandleMethodNotAllowed && len(t) > 0 { // According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response // containing a list of the target resource's currently supported methods. allowed := make([]string, 0, len(t)-1) diff --git a/gin_test.go b/gin_test.go index e68f1ce..db70a8c 100644 --- a/gin_test.go +++ b/gin_test.go @@ -754,3 +754,14 @@ func TestCustomUnmarshalStruct(t *testing.T) { assert.Equal(t, 200, w.Code) assert.Equal(t, `"2000/01/01"`, w.Body.String()) } + +// Test the fix for https://github.com/gin-gonic/gin/issues/4002 +func TestMethodNotAllowedNoRoute(t *testing.T) { + g := New() + g.HandleMethodNotAllowed = true + + req := httptest.NewRequest("GET", "/", nil) + resp := httptest.NewRecorder() + assert.NotPanics(t, func() { g.ServeHTTP(resp, req) }) + assert.Equal(t, http.StatusNotFound, resp.Code) +} From 5f55c6a711376c77834bc6b25d35c8985de1d311 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sun, 14 Jul 2024 14:33:08 +0200 Subject: [PATCH 881/912] ci(lint): enable testifylint linter (#4010) Signed-off-by: Matthieu MOREL --- .golangci.yml | 3 + binding/binding_msgpack_test.go | 7 +- binding/binding_test.go | 259 +++++++++++++------------ binding/form_mapping_test.go | 79 ++++---- binding/multipart_form_mapping_test.go | 21 +- binding/validate_test.go | 39 ++-- context_test.go | 239 +++++++++++------------ debug_test.go | 9 +- errors_test.go | 5 +- fs_test.go | 7 +- gin_integration_test.go | 89 ++++----- gin_test.go | 27 +-- githubapi_test.go | 15 +- logger_test.go | 12 +- path_test.go | 2 +- render/render_msgpack_test.go | 5 +- render/render_test.go | 75 +++---- response_writer_test.go | 9 +- routes_test.go | 7 +- utils_test.go | 4 +- 20 files changed, 461 insertions(+), 452 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 5a65972..8d58c98 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,6 +17,7 @@ linters: - nilerr - nolintlint - revive + - testifylint - wastedassign linters-settings: @@ -33,6 +34,8 @@ linters-settings: - G112 - G201 - G203 + testifylint: + enable-all: true issues: exclude-rules: diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go index a6cd6aa..a811639 100644 --- a/binding/binding_msgpack_test.go +++ b/binding/binding_msgpack_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/ugorji/go/codec" ) @@ -24,7 +25,7 @@ func TestBindingMsgPack(t *testing.T) { buf := bytes.NewBuffer([]byte{}) assert.NotNil(t, buf) err := codec.NewEncoder(buf, h).Encode(test) - assert.NoError(t, err) + require.NoError(t, err) data := buf.Bytes() @@ -41,14 +42,14 @@ func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEMSGPACK) err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEMSGPACK) err = MsgPack.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingDefaultMsgPack(t *testing.T) { diff --git a/binding/binding_test.go b/binding/binding_test.go index c59e5e9..2036b59 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -20,6 +20,7 @@ import ( "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) @@ -175,7 +176,7 @@ func TestBindingJSONNilBody(t *testing.T) { var obj FooStruct req, _ := http.NewRequest(http.MethodPost, "/", nil) err := JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingJSON(t *testing.T) { @@ -376,7 +377,7 @@ func TestBindingFormStringSliceMap(t *testing.T) { req := requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") req.Header.Add("Content-Type", MIMEPOSTForm) err := Form.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, obj) assert.Len(t, obj, 2) target := map[string][]string{ @@ -389,7 +390,7 @@ func TestBindingFormStringSliceMap(t *testing.T) { req = requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") req.Header.Add("Content-Type", MIMEPOSTForm) err = Form.Bind(req, &objInvalid) - assert.Error(t, err) + require.Error(t, err) } func TestBindingQuery(t *testing.T) { @@ -428,7 +429,7 @@ func TestBindingQueryStringMap(t *testing.T) { obj := make(map[string]string) req := requestWithBody("GET", "/?foo=bar&hello=world", "") err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, obj) assert.Len(t, obj, 2) assert.Equal(t, "bar", obj["foo"]) @@ -437,7 +438,7 @@ func TestBindingQueryStringMap(t *testing.T) { obj = make(map[string]string) req = requestWithBody("GET", "/?foo=bar&foo=2&hello=world", "") // should pick last err = b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, obj) assert.Len(t, obj, 2) assert.Equal(t, "2", obj["foo"]) @@ -495,28 +496,28 @@ func TestBindingYAMLFail(t *testing.T) { func createFormPostRequest(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createDefaultFormPostRequest(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMap(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMapFail(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } @@ -527,20 +528,20 @@ func createFormFilesMultipartRequest(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("foo", "bar")) - assert.NoError(t, mw.WriteField("bar", "foo")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("foo", "bar")) + require.NoError(t, mw.WriteField("bar", "foo")) f, err := os.Open("form.go") - assert.NoError(t, err) + require.NoError(t, err) defer f.Close() fw, err1 := mw.CreateFormFile("file", "form.go") - assert.NoError(t, err1) + require.NoError(t, err1) _, err = io.Copy(fw, f) - assert.NoError(t, err) + require.NoError(t, err) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) - assert.NoError(t, err2) + require.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -552,20 +553,20 @@ func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("foo", "bar")) - assert.NoError(t, mw.WriteField("bar", "foo")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("foo", "bar")) + require.NoError(t, mw.WriteField("bar", "foo")) f, err := os.Open("form.go") - assert.NoError(t, err) + require.NoError(t, err) defer f.Close() fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go") - assert.NoError(t, err1) + require.NoError(t, err1) _, err = io.Copy(fw, f) - assert.NoError(t, err) + require.NoError(t, err) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) - assert.NoError(t, err2) + require.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -577,11 +578,11 @@ func createFormMultipartRequest(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("foo", "bar")) - assert.NoError(t, mw.WriteField("bar", "foo")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("foo", "bar")) + require.NoError(t, mw.WriteField("bar", "foo")) req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -592,10 +593,10 @@ func createFormMultipartRequestForMap(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -606,10 +607,10 @@ func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { mw := multipart.NewWriter(body) defer mw.Close() - assert.NoError(t, mw.SetBoundary(boundary)) - assert.NoError(t, mw.WriteField("map_foo", "3.14")) + require.NoError(t, mw.SetBoundary(boundary)) + require.NoError(t, mw.WriteField("map_foo", "3.14")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -617,7 +618,7 @@ func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { func TestBindingFormPost(t *testing.T) { req := createFormPostRequest(t) var obj FooBarStruct - assert.NoError(t, FormPost.Bind(req, &obj)) + require.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "form-urlencoded", FormPost.Name()) assert.Equal(t, "bar", obj.Foo) @@ -627,7 +628,7 @@ func TestBindingFormPost(t *testing.T) { func TestBindingDefaultValueFormPost(t *testing.T) { req := createDefaultFormPostRequest(t) var obj FooDefaultBarStruct - assert.NoError(t, FormPost.Bind(req, &obj)) + require.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) @@ -637,22 +638,22 @@ func TestBindingFormPostForMap(t *testing.T) { req := createFormPostRequestForMap(t) var obj FooStructForMapType err := FormPost.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) + require.NoError(t, err) + assert.InDelta(t, float64(123), obj.MapFoo["bar"].(float64), 0.01) } func TestBindingFormPostForMapFail(t *testing.T) { req := createFormPostRequestForMapFail(t) var obj FooStructForMapType err := FormPost.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingFormFilesMultipart(t *testing.T) { req := createFormFilesMultipartRequest(t) var obj FooBarFileStruct err := FormMultipart.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) // file from os f, _ := os.Open("form.go") @@ -664,9 +665,9 @@ func TestBindingFormFilesMultipart(t *testing.T) { defer mf.Close() fileExpect, _ := io.ReadAll(mf) - assert.Equal(t, FormMultipart.Name(), "multipart/form-data") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "multipart/form-data", FormMultipart.Name()) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) assert.Equal(t, fileExpect, fileActual) } @@ -674,13 +675,13 @@ func TestBindingFormFilesMultipartFail(t *testing.T) { req := createFormFilesMultipartRequestFail(t) var obj FooBarFileFailStruct err := FormMultipart.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest(t) var obj FooBarStruct - assert.NoError(t, FormMultipart.Bind(req, &obj)) + require.NoError(t, FormMultipart.Bind(req, &obj)) assert.Equal(t, "multipart/form-data", FormMultipart.Name()) assert.Equal(t, "bar", obj.Foo) @@ -691,17 +692,17 @@ func TestBindingFormMultipartForMap(t *testing.T) { req := createFormMultipartRequestForMap(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) + require.NoError(t, err) + assert.InDelta(t, float64(123), obj.MapFoo["bar"].(float64), 0.01) assert.Equal(t, "thinkerou", obj.MapFoo["name"].(string)) - assert.Equal(t, float64(3.14), obj.MapFoo["pai"].(float64)) + assert.InDelta(t, float64(3.14), obj.MapFoo["pai"].(float64), 0.01) } func TestBindingFormMultipartForMapFail(t *testing.T) { req := createFormMultipartRequestForMapFail(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestBindingProtoBuf(t *testing.T) { @@ -732,7 +733,7 @@ func TestValidationFails(t *testing.T) { var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestValidationDisabled(t *testing.T) { @@ -743,7 +744,7 @@ func TestValidationDisabled(t *testing.T) { var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) } func TestRequiredSucceeds(t *testing.T) { @@ -754,7 +755,7 @@ func TestRequiredSucceeds(t *testing.T) { var obj HogeStruct req := requestWithBody("POST", "/", `{"hoge": 0}`) err := JSON.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) } func TestRequiredFails(t *testing.T) { @@ -765,7 +766,7 @@ func TestRequiredFails(t *testing.T) { var obj HogeStruct req := requestWithBody("POST", "/", `{"boen": 0}`) err := JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestHeaderBinding(t *testing.T) { @@ -779,7 +780,7 @@ func TestHeaderBinding(t *testing.T) { var theader tHeader req := requestWithBody("GET", "/", "") req.Header.Add("limit", "1000") - assert.NoError(t, h.Bind(req, &theader)) + require.NoError(t, h.Bind(req, &theader)) assert.Equal(t, 1000, theader.Limit) req = requestWithBody("GET", "/", "") @@ -790,7 +791,7 @@ func TestHeaderBinding(t *testing.T) { } err := h.Bind(req, &failStruct{}) - assert.Error(t, err) + require.Error(t, err) } func TestUriBinding(t *testing.T) { @@ -803,14 +804,14 @@ func TestUriBinding(t *testing.T) { var tag Tag m := make(map[string][]string) m["name"] = []string{"thinkerou"} - assert.NoError(t, b.BindUri(m, &tag)) + require.NoError(t, b.BindUri(m, &tag)) assert.Equal(t, "thinkerou", tag.Name) type NotSupportStruct struct { Name map[string]any `uri:"name"` } var not NotSupportStruct - assert.Error(t, b.BindUri(m, ¬)) + require.Error(t, b.BindUri(m, ¬)) assert.Equal(t, map[string]any(nil), not.Name) } @@ -831,9 +832,9 @@ func TestUriInnerBinding(t *testing.T) { } var tag Tag - assert.NoError(t, Uri.BindUri(m, &tag)) - assert.Equal(t, tag.Name, expectedName) - assert.Equal(t, tag.S.Age, expectedAge) + require.NoError(t, Uri.BindUri(m, &tag)) + assert.Equal(t, expectedName, tag.Name) + assert.Equal(t, expectedAge, tag.S.Age) } func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, badBody string) { @@ -846,7 +847,7 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 1, obj.Page) assert.Equal(t, 2, obj.Size) assert.Equal(t, "test-appkey", obj.Appkey) @@ -862,14 +863,14 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo", obj.Bar) obj = FooBarStruct{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badBody string) { @@ -882,14 +883,14 @@ func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badB req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) obj = FooDefaultBarStruct{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestFormBindingFail(t *testing.T) { @@ -899,18 +900,18 @@ func TestFormBindingFail(t *testing.T) { obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestFormBindingMultipartFail(t *testing.T) { obj := FooBarStruct{} req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar")) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary") _, err = req.MultipartReader() - assert.NoError(t, err) + require.NoError(t, err) err = Form.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestFormPostBindingFail(t *testing.T) { @@ -920,7 +921,7 @@ func TestFormPostBindingFail(t *testing.T) { obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func TestFormMultipartBindingFail(t *testing.T) { @@ -930,7 +931,7 @@ func TestFormMultipartBindingFail(t *testing.T) { obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) { @@ -944,7 +945,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, int64(1510675200), obj.TimeFoo.Unix()) assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String()) assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix()) @@ -955,7 +956,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, body, badBody string) { @@ -968,12 +969,12 @@ func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, bo req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForTimeTypeNotUnixFormat{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { @@ -986,12 +987,12 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForTimeTypeNotFormat{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) { @@ -1004,12 +1005,12 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForTimeTypeFailFormat{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) { @@ -1022,12 +1023,12 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForTimeTypeFailLocation{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBody string) { @@ -1040,7 +1041,7 @@ func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBo req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Nil(t, obj.Foo) } @@ -1055,13 +1056,13 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "", obj.TestName) obj = InvalidNameType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) { @@ -1074,12 +1075,12 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) obj = InvalidNameMapType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) { @@ -1094,17 +1095,17 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s case "Slice": obj := FooStructForSliceType{} err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, []int{1, 2}, obj.SliceFoo) obj = FooStructForSliceType{} req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) case "Struct": obj := FooStructForStructType{} err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, struct { Idx int "form:\"idx\"" @@ -1113,7 +1114,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s case "StructPointer": obj := FooStructForStructPointerType{} err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, struct { Name string "form:\"name\"" @@ -1122,33 +1123,33 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s case "Map": obj := FooStructForMapType{} err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64)) + require.NoError(t, err) + assert.InDelta(t, float64(123), obj.MapFoo["bar"].(float64), 0.01) case "SliceMap": obj := FooStructForSliceMapType{} err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) case "Ptr": obj := FooStructForStringPtrType{} err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Nil(t, obj.PtrFoo) assert.Equal(t, "test", *obj.PtrBar) obj = FooStructForStringPtrType{} obj.PtrBar = new(string) err = b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "test", *obj.PtrBar) objErr := FooStructForMapPtrType{} err = b.Bind(req, &objErr) - assert.Error(t, err) + require.Error(t, err) obj = FooStructForStringPtrType{} req = requestWithBody(method, badPath, badBody) err = b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } } @@ -1162,7 +1163,7 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo", obj.Bar) } @@ -1177,7 +1178,7 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody string) { @@ -1190,7 +1191,7 @@ func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1199,13 +1200,13 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1214,12 +1215,12 @@ func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, ba var obj1 []FooStruct req := requestWithBody("POST", path, body) err := b.Bind(req, &obj1) - assert.NoError(t, err) + require.NoError(t, err) var obj2 []FooStruct req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj2) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) { @@ -1229,7 +1230,7 @@ func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badB req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, obj) assert.Len(t, obj, 2) assert.Equal(t, "bar", obj["foo"]) @@ -1239,13 +1240,13 @@ func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badB obj = make(map[string]string) req = requestWithBody("POST", badPath, badBody) err = b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } objInt := make(map[string]int) req = requestWithBody("POST", path, body) err = b.Bind(req, &objInt) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1255,16 +1256,16 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body req := requestWithBody("POST", path, body) EnableDecoderUseNumber = true err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) // we hope it is int64(123) v, e := obj.Foo.(json.Number).Int64() - assert.NoError(t, e) + require.NoError(t, e) assert.Equal(t, int64(123), v) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1274,15 +1275,15 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod req := requestWithBody("POST", path, body) EnableDecoderUseNumber = false err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) // it will return float64(123) if not use EnableDecoderUseNumber // maybe it is not hoped - assert.Equal(t, float64(123), obj.Foo) + assert.InDelta(t, float64(123), obj.Foo, 0.01) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath, body, badBody string) { @@ -1294,13 +1295,13 @@ func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath obj := FooStructDisallowUnknownFields{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStructDisallowUnknownFields{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "what") } @@ -1310,13 +1311,13 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) assert.Equal(t, "", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) err = JSON.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1326,14 +1327,14 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "yes", *obj.Label) obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } type hook struct{} @@ -1358,28 +1359,28 @@ func TestPlainBinding(t *testing.T) { var s string req := requestWithBody("POST", "/", "test string") - assert.NoError(t, p.Bind(req, &s)) - assert.Equal(t, s, "test string") + require.NoError(t, p.Bind(req, &s)) + assert.Equal(t, "test string", s) var bs []byte req = requestWithBody("POST", "/", "test []byte") - assert.NoError(t, p.Bind(req, &bs)) + require.NoError(t, p.Bind(req, &bs)) assert.Equal(t, bs, []byte("test []byte")) var i int req = requestWithBody("POST", "/", "test fail") - assert.Error(t, p.Bind(req, &i)) + require.Error(t, p.Bind(req, &i)) req = requestWithBody("POST", "/", "") req.Body = &failRead{} - assert.Error(t, p.Bind(req, &s)) + require.Error(t, p.Bind(req, &s)) req = requestWithBody("POST", "/", "") - assert.Nil(t, p.Bind(req, nil)) + require.NoError(t, p.Bind(req, nil)) var ptr *string req = requestWithBody("POST", "/", "") - assert.Nil(t, p.Bind(req, ptr)) + require.NoError(t, p.Bind(req, ptr)) } func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { @@ -1391,20 +1392,20 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body req.Body = io.NopCloser(&hook{}) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) invalidobj := FooStruct{} req.Body = io.NopCloser(strings.NewReader(`{"msg":"hello"}`)) req.Header.Add("Content-Type", MIMEPROTOBUF) err = b.Bind(req, &invalidobj) - assert.Error(t, err) - assert.Equal(t, err.Error(), "obj is not ProtoMessage") + require.Error(t, err) + assert.Equal(t, "obj is not ProtoMessage", err.Error()) obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) - assert.Error(t, err) + require.Error(t, err) } func requestWithBody(method, path, body string) (req *http.Request) { diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index afd51f9..9ea0895 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -15,6 +15,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMappingBaseTypes(t *testing.T) { @@ -59,7 +60,7 @@ func TestMappingBaseTypes(t *testing.T) { field := val.Elem().Type().Field(0) _, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, "form") - assert.NoError(t, err, testName) + require.NoError(t, err, testName) actual := val.Elem().Field(0).Interface() assert.Equal(t, tt.expect, actual, testName) @@ -73,7 +74,7 @@ func TestMappingDefault(t *testing.T) { Array [1]int `form:",default=9"` } err := mappingByPtr(&s, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 9, s.Int) assert.Equal(t, []int{9}, s.Slice) @@ -85,7 +86,7 @@ func TestMappingSkipField(t *testing.T) { A int } err := mappingByPtr(&s, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 0, s.A) } @@ -96,7 +97,7 @@ func TestMappingIgnoreField(t *testing.T) { B int `form:"-"` } err := mappingByPtr(&s, formSource{"A": {"9"}, "B": {"9"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 9, s.A) assert.Equal(t, 0, s.B) @@ -108,7 +109,7 @@ func TestMappingUnexportedField(t *testing.T) { b int `form:"b"` } err := mappingByPtr(&s, formSource{"a": {"9"}, "b": {"9"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 9, s.A) assert.Equal(t, 0, s.b) @@ -119,7 +120,7 @@ func TestMappingPrivateField(t *testing.T) { f int `form:"field"` } err := mappingByPtr(&s, formSource{"field": {"6"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 0, s.f) } @@ -129,7 +130,7 @@ func TestMappingUnknownFieldType(t *testing.T) { } err := mappingByPtr(&s, formSource{"U": {"unknown"}}, "form") - assert.Error(t, err) + require.Error(t, err) assert.Equal(t, errUnknownType, err) } @@ -138,7 +139,7 @@ func TestMappingURI(t *testing.T) { F int `uri:"field"` } err := mapURI(&s, map[string][]string{"field": {"6"}}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 6, s.F) } @@ -147,7 +148,7 @@ func TestMappingForm(t *testing.T) { F int `form:"field"` } err := mapForm(&s, map[string][]string{"field": {"6"}}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 6, s.F) } @@ -156,7 +157,7 @@ func TestMapFormWithTag(t *testing.T) { F int `externalTag:"field"` } err := MapFormWithTag(&s, map[string][]string{"field": {"6"}}, "externalTag") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 6, s.F) } @@ -171,7 +172,7 @@ func TestMappingTime(t *testing.T) { var err error time.Local, err = time.LoadLocation("Europe/Berlin") - assert.NoError(t, err) + require.NoError(t, err) err = mapForm(&s, map[string][]string{ "Time": {"2019-01-20T16:02:58Z"}, @@ -180,7 +181,7 @@ func TestMappingTime(t *testing.T) { "CSTTime": {"2019-01-20"}, "UTCTime": {"2019-01-20"}, }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "2019-01-20 16:02:58 +0000 UTC", s.Time.String()) assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) @@ -195,14 +196,14 @@ func TestMappingTime(t *testing.T) { Time time.Time `time_location:"wrong"` } err = mapForm(&wrongLoc, map[string][]string{"Time": {"2019-01-20T16:02:58Z"}}) - assert.Error(t, err) + require.Error(t, err) // wrong time value var wrongTime struct { Time time.Time } err = mapForm(&wrongTime, map[string][]string{"Time": {"wrong"}}) - assert.Error(t, err) + require.Error(t, err) } func TestMappingTimeDuration(t *testing.T) { @@ -212,12 +213,12 @@ func TestMappingTimeDuration(t *testing.T) { // ok err := mappingByPtr(&s, formSource{"D": {"5s"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 5*time.Second, s.D) // error err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form") - assert.Error(t, err) + require.Error(t, err) } func TestMappingSlice(t *testing.T) { @@ -227,17 +228,17 @@ func TestMappingSlice(t *testing.T) { // default value err := mappingByPtr(&s, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, []int{9}, s.Slice) // ok err = mappingByPtr(&s, formSource{"slice": {"3", "4"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, []int{3, 4}, s.Slice) // error err = mappingByPtr(&s, formSource{"slice": {"wrong"}}, "form") - assert.Error(t, err) + require.Error(t, err) } func TestMappingArray(t *testing.T) { @@ -247,20 +248,20 @@ func TestMappingArray(t *testing.T) { // wrong default err := mappingByPtr(&s, formSource{}, "form") - assert.Error(t, err) + require.Error(t, err) // ok err = mappingByPtr(&s, formSource{"array": {"3", "4"}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, [2]int{3, 4}, s.Array) // error - not enough vals err = mappingByPtr(&s, formSource{"array": {"3"}}, "form") - assert.Error(t, err) + require.Error(t, err) // error - wrong value err = mappingByPtr(&s, formSource{"array": {"wrong"}}, "form") - assert.Error(t, err) + require.Error(t, err) } func TestMappingStructField(t *testing.T) { @@ -271,7 +272,7 @@ func TestMappingStructField(t *testing.T) { } err := mappingByPtr(&s, formSource{"J": {`{"I": 9}`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 9, s.J.I) } @@ -289,20 +290,20 @@ func TestMappingPtrField(t *testing.T) { // With 0 items. var req0 ptrRequest err = mappingByPtr(&req0, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Empty(t, req0.Items) // With 1 item. var req1 ptrRequest err = mappingByPtr(&req1, formSource{"items": {`{"key": 1}`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, req1.Items, 1) assert.EqualValues(t, 1, req1.Items[0].Key) // With 2 items. var req2 ptrRequest err = mappingByPtr(&req2, formSource{"items": {`{"key": 1}`, `{"key": 2}`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, req2.Items, 2) assert.EqualValues(t, 1, req2.Items[0].Key) assert.EqualValues(t, 2, req2.Items[1].Key) @@ -314,7 +315,7 @@ func TestMappingMapField(t *testing.T) { } err := mappingByPtr(&s, formSource{"M": {`{"one": 1}`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, map[string]int{"one": 1}, s.M) } @@ -325,7 +326,7 @@ func TestMappingIgnoredCircularRef(t *testing.T) { var s S err := mappingByPtr(&s, formSource{}, "form") - assert.NoError(t, err) + require.NoError(t, err) } type customUnmarshalParamHex int @@ -344,7 +345,7 @@ func TestMappingCustomUnmarshalParamHexWithFormTag(t *testing.T) { Foo customUnmarshalParamHex `form:"foo"` } err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 245, s.Foo) } @@ -354,7 +355,7 @@ func TestMappingCustomUnmarshalParamHexWithURITag(t *testing.T) { Foo customUnmarshalParamHex `uri:"foo"` } err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, 245, s.Foo) } @@ -381,7 +382,7 @@ func TestMappingCustomStructTypeWithFormTag(t *testing.T) { FileData customUnmarshalParamType `form:"data"` } err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "file", s.FileData.Protocol) assert.EqualValues(t, "/foo", s.FileData.Path) @@ -393,7 +394,7 @@ func TestMappingCustomStructTypeWithURITag(t *testing.T) { FileData customUnmarshalParamType `uri:"data"` } err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "file", s.FileData.Protocol) assert.EqualValues(t, "/foo", s.FileData.Path) @@ -405,7 +406,7 @@ func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) { FileData *customUnmarshalParamType `form:"data"` } err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "file", s.FileData.Protocol) assert.EqualValues(t, "/foo", s.FileData.Path) @@ -417,7 +418,7 @@ func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) { FileData *customUnmarshalParamType `uri:"data"` } err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "file", s.FileData.Protocol) assert.EqualValues(t, "/foo", s.FileData.Path) @@ -442,7 +443,7 @@ func TestMappingCustomSliceUri(t *testing.T) { FileData customPath `uri:"path"` } err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "bar", s.FileData[0]) assert.EqualValues(t, "foo", s.FileData[1]) @@ -453,7 +454,7 @@ func TestMappingCustomSliceForm(t *testing.T) { FileData customPath `form:"path"` } err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form") - assert.NoError(t, err) + require.NoError(t, err) assert.EqualValues(t, "bar", s.FileData[0]) assert.EqualValues(t, "foo", s.FileData[1]) @@ -492,7 +493,7 @@ func TestMappingCustomArrayUri(t *testing.T) { } val := `664a062ac74a8ad104e0e80f` err := mappingByPtr(&s, formSource{"id": {val}}, "uri") - assert.NoError(t, err) + require.NoError(t, err) expected, _ := convertTo(val) assert.EqualValues(t, expected, s.FileData) @@ -504,7 +505,7 @@ func TestMappingCustomArrayForm(t *testing.T) { } val := `664a062ac74a8ad104e0e80f` err := mappingByPtr(&s, formSource{"id": {val}}, "form") - assert.NoError(t, err) + require.NoError(t, err) expected, _ := convertTo(val) assert.EqualValues(t, expected, s.FileData) diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go index 4e97c0f..9782b81 100644 --- a/binding/multipart_form_mapping_test.go +++ b/binding/multipart_form_mapping_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestFormMultipartBindingBindOneFile(t *testing.T) { @@ -27,7 +28,7 @@ func TestFormMultipartBindingBindOneFile(t *testing.T) { req := createRequestMultipartFiles(t, file) err := FormMultipart.Bind(req, &s) - assert.NoError(t, err) + require.NoError(t, err) assertMultipartFileHeader(t, &s.FileValue, file) assertMultipartFileHeader(t, s.FilePtr, file) @@ -53,7 +54,7 @@ func TestFormMultipartBindingBindTwoFiles(t *testing.T) { req := createRequestMultipartFiles(t, files...) err := FormMultipart.Bind(req, &s) - assert.NoError(t, err) + require.NoError(t, err) assert.Len(t, s.SliceValues, len(files)) assert.Len(t, s.SlicePtrs, len(files)) @@ -90,7 +91,7 @@ func TestFormMultipartBindingBindError(t *testing.T) { } { req := createRequestMultipartFiles(t, files...) err := FormMultipart.Bind(req, tt.s) - assert.Error(t, err) + require.Error(t, err) } } @@ -106,17 +107,17 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request mw := multipart.NewWriter(&body) for _, file := range files { fw, err := mw.CreateFormFile(file.Fieldname, file.Filename) - assert.NoError(t, err) + require.NoError(t, err) n, err := fw.Write(file.Content) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, len(file.Content), n) } err := mw.Close() - assert.NoError(t, err) + require.NoError(t, err) req, err := http.NewRequest("POST", "/", &body) - assert.NoError(t, err) + require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary()) return req @@ -127,12 +128,12 @@ func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file test assert.Equal(t, int64(len(file.Content)), fh.Size) fl, err := fh.Open() - assert.NoError(t, err) + require.NoError(t, err) body, err := io.ReadAll(fl) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, string(file.Content), string(body)) err = fl.Close() - assert.NoError(t, err) + require.NoError(t, err) } diff --git a/binding/validate_test.go b/binding/validate_test.go index 1fc15ff..c9bbe60 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -11,6 +11,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type testInterface interface { @@ -113,10 +114,10 @@ func TestValidateNoValidationValues(t *testing.T) { test := createNoValidationValues() empty := structNoValidationValues{} - assert.Nil(t, validate(test)) - assert.Nil(t, validate(&test)) - assert.Nil(t, validate(empty)) - assert.Nil(t, validate(&empty)) + require.NoError(t, validate(test)) + require.NoError(t, validate(&test)) + require.NoError(t, validate(empty)) + require.NoError(t, validate(&empty)) assert.Equal(t, origin, test) } @@ -163,8 +164,8 @@ func TestValidateNoValidationPointers(t *testing.T) { //assert.Nil(t, validate(test)) //assert.Nil(t, validate(&test)) - assert.Nil(t, validate(empty)) - assert.Nil(t, validate(&empty)) + require.NoError(t, validate(empty)) + require.NoError(t, validate(&empty)) //assert.Equal(t, origin, test) } @@ -173,22 +174,22 @@ type Object map[string]any func TestValidatePrimitives(t *testing.T) { obj := Object{"foo": "bar", "bar": 1} - assert.NoError(t, validate(obj)) - assert.NoError(t, validate(&obj)) + require.NoError(t, validate(obj)) + require.NoError(t, validate(&obj)) assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj) obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}} - assert.NoError(t, validate(obj2)) - assert.NoError(t, validate(&obj2)) + require.NoError(t, validate(obj2)) + require.NoError(t, validate(&obj2)) nu := 10 - assert.NoError(t, validate(nu)) - assert.NoError(t, validate(&nu)) + require.NoError(t, validate(nu)) + require.NoError(t, validate(&nu)) assert.Equal(t, 10, nu) str := "value" - assert.NoError(t, validate(str)) - assert.NoError(t, validate(&str)) + require.NoError(t, validate(str)) + require.NoError(t, validate(&str)) assert.Equal(t, "value", str) } @@ -212,8 +213,8 @@ func TestValidateAndModifyStruct(t *testing.T) { s := structModifyValidation{Integer: 1} errs := validate(&s) - assert.Nil(t, errs) - assert.Equal(t, s, structModifyValidation{Integer: 0}) + require.NoError(t, errs) + assert.Equal(t, structModifyValidation{Integer: 0}, s) } // structCustomValidation is a helper struct we use to check that @@ -239,14 +240,14 @@ func TestValidatorEngine(t *testing.T) { err := engine.RegisterValidation("notone", notOne) // Check that we can register custom validation without error - assert.Nil(t, err) + require.NoError(t, err) // Create an instance which will fail validation withOne := structCustomValidation{Integer: 1} errs := validate(withOne) // Check that we got back non-nil errs - assert.NotNil(t, errs) + require.Error(t, errs) // Check that the error matches expectation - assert.Error(t, errs, "", "", "notone") + require.Error(t, errs, "", "", "notone") } diff --git a/context_test.go b/context_test.go index 8bbf270..66190b3 100644 --- a/context_test.go +++ b/context_test.go @@ -27,6 +27,7 @@ import ( "github.com/gin-gonic/gin/binding" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) @@ -74,20 +75,18 @@ func TestContextFormFile(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") - if assert.NoError(t, err) { - _, err = w.Write([]byte("test")) - assert.NoError(t, err) - } + require.NoError(t, err) + _, err = w.Write([]byte("test")) + require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") - if assert.NoError(t, err) { - assert.Equal(t, "test", f.Filename) - } + require.NoError(t, err) + assert.Equal(t, "test", f.Filename) - assert.NoError(t, c.SaveUploadedFile(f, "test")) + require.NoError(t, c.SaveUploadedFile(f, "test")) } func TestContextFormFileFailed(t *testing.T) { @@ -99,29 +98,27 @@ func TestContextFormFileFailed(t *testing.T) { c.Request.Header.Set("Content-Type", mw.FormDataContentType()) c.engine.MaxMultipartMemory = 8 << 20 f, err := c.FormFile("file") - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, f) } func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) - assert.NoError(t, mw.WriteField("foo", "bar")) + require.NoError(t, mw.WriteField("foo", "bar")) w, err := mw.CreateFormFile("file", "test") - if assert.NoError(t, err) { - _, err = w.Write([]byte("test")) - assert.NoError(t, err) - } + require.NoError(t, err) + _, err = w.Write([]byte("test")) + require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.MultipartForm() - if assert.NoError(t, err) { - assert.NotNil(t, f) - } + require.NoError(t, err) + assert.NotNil(t, f) - assert.NoError(t, c.SaveUploadedFile(f.File["file"][0], "test")) + require.NoError(t, c.SaveUploadedFile(f.File["file"][0], "test")) } func TestSaveUploadedOpenFailed(t *testing.T) { @@ -136,27 +133,25 @@ func TestSaveUploadedOpenFailed(t *testing.T) { f := &multipart.FileHeader{ Filename: "file", } - assert.Error(t, c.SaveUploadedFile(f, "test")) + require.Error(t, c.SaveUploadedFile(f, "test")) } func TestSaveUploadedCreateFailed(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") - if assert.NoError(t, err) { - _, err = w.Write([]byte("test")) - assert.NoError(t, err) - } + require.NoError(t, err) + _, err = w.Write([]byte("test")) + require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") - if assert.NoError(t, err) { - assert.Equal(t, "test", f.Filename) - } + require.NoError(t, err) + assert.Equal(t, "test", f.Filename) - assert.Error(t, c.SaveUploadedFile(f, "/")) + require.Error(t, c.SaveUploadedFile(f, "/")) } func TestContextReset(t *testing.T) { @@ -174,10 +169,10 @@ func TestContextReset(t *testing.T) { assert.False(t, c.IsAborted()) assert.Nil(t, c.Keys) assert.Nil(t, c.Accepted) - assert.Len(t, c.Errors, 0) + assert.Empty(t, c.Errors) assert.Empty(t, c.Errors.Errors()) assert.Empty(t, c.Errors.ByType(ErrorTypeAny)) - assert.Len(t, c.Params, 0) + assert.Empty(t, c.Params) assert.EqualValues(t, c.index, -1) assert.Equal(t, c.Writer.(*responseWriter), &c.writermem) } @@ -230,13 +225,13 @@ func TestContextSetGetValues(t *testing.T) { var a any = 1 c.Set("intInterface", a) - assert.Exactly(t, c.MustGet("string").(string), "this is a string") + assert.Exactly(t, "this is a string", c.MustGet("string").(string)) assert.Exactly(t, c.MustGet("int32").(int32), int32(-42)) - assert.Exactly(t, c.MustGet("int64").(int64), int64(42424242424242)) - assert.Exactly(t, c.MustGet("uint64").(uint64), uint64(42)) - assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2)) - assert.Exactly(t, c.MustGet("float64").(float64), 4.2) - assert.Exactly(t, c.MustGet("intInterface").(int), 1) + assert.Exactly(t, int64(42424242424242), c.MustGet("int64").(int64)) + assert.Exactly(t, uint64(42), c.MustGet("uint64").(uint64)) + assert.InDelta(t, float32(4.2), c.MustGet("float32").(float32), 0.01) + assert.InDelta(t, 4.2, c.MustGet("float64").(float64), 0.01) + assert.Exactly(t, 1, c.MustGet("intInterface").(int)) } func TestContextGetString(t *testing.T) { @@ -278,7 +273,7 @@ func TestContextGetUint64(t *testing.T) { func TestContextGetFloat64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("float64", 4.2) - assert.Equal(t, 4.2, c.GetFloat64("float64")) + assert.InDelta(t, 4.2, c.GetFloat64("float64"), 0.01) } func TestContextGetTime(t *testing.T) { @@ -344,12 +339,12 @@ func TestContextCopy(t *testing.T) { assert.Nil(t, cp.writermem.ResponseWriter) assert.Equal(t, &cp.writermem, cp.Writer.(*responseWriter)) assert.Equal(t, cp.Request, c.Request) - assert.Equal(t, cp.index, abortIndex) + assert.Equal(t, abortIndex, cp.index) assert.Equal(t, cp.Keys, c.Keys) assert.Equal(t, cp.engine, c.engine) assert.Equal(t, cp.Params, c.Params) cp.Set("foo", "notBar") - assert.False(t, cp.Keys["foo"] == c.Keys["foo"]) + assert.NotEqual(t, cp.Keys["foo"], c.Keys["foo"]) assert.Equal(t, cp.fullPath, c.fullPath) } @@ -366,7 +361,7 @@ func TestContextHandlerNames(t *testing.T) { names := c.HandlerNames() - assert.True(t, len(names) == 4) + assert.Len(t, names, 4) for _, name := range names { assert.Regexp(t, `^(.*/vendor/)?(github\.com/gin-gonic/gin\.){1}(TestContextHandlerNames\.func.*){0,1}(handlerNameTest.*){0,1}`, name) } @@ -425,7 +420,7 @@ func TestContextQuery(t *testing.T) { func TestContextInitQueryCache(t *testing.T) { validURL, err := url.Parse("https://github.com/gin-gonic/gin/pull/3969?key=value&otherkey=othervalue") - assert.Nil(t, err) + require.NoError(t, err) tests := []struct { testName string @@ -531,7 +526,7 @@ func TestContextQueryAndPostForm(t *testing.T) { Both string `form:"both"` Array []string `form:"array[]"` } - assert.NoError(t, c.Bind(&obj)) + require.NoError(t, c.Bind(&obj)) assert.Equal(t, "bar", obj.Foo, "bar") assert.Equal(t, "main", obj.ID, "main") assert.Equal(t, 11, obj.Page, 11) @@ -548,10 +543,10 @@ func TestContextQueryAndPostForm(t *testing.T) { assert.Equal(t, "second", values[1]) values = c.QueryArray("nokey") - assert.Equal(t, 0, len(values)) + assert.Empty(t, values) values = c.QueryArray("both") - assert.Equal(t, 1, len(values)) + assert.Len(t, values, 1) assert.Equal(t, "GET", values[0]) dicts, ok := c.GetQueryMap("ids") @@ -561,22 +556,22 @@ func TestContextQueryAndPostForm(t *testing.T) { dicts, ok = c.GetQueryMap("nokey") assert.False(t, ok) - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) dicts, ok = c.GetQueryMap("both") assert.False(t, ok) - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) dicts, ok = c.GetQueryMap("array") assert.False(t, ok) - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) dicts = c.QueryMap("ids") assert.Equal(t, "hi", dicts["a"]) assert.Equal(t, "3.14", dicts["b"]) dicts = c.QueryMap("nokey") - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) } func TestContextPostFormMultipart(t *testing.T) { @@ -594,7 +589,7 @@ func TestContextPostFormMultipart(t *testing.T) { TimeLocation time.Time `form:"time_location" time_format:"02/01/2006 15:04" time_location:"Asia/Tokyo"` BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"` } - assert.NoError(t, c.Bind(&obj)) + require.NoError(t, c.Bind(&obj)) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "10", obj.Bar) assert.Equal(t, 10, obj.BarAsInt) @@ -648,10 +643,10 @@ func TestContextPostFormMultipart(t *testing.T) { assert.Equal(t, "second", values[1]) values = c.PostFormArray("nokey") - assert.Equal(t, 0, len(values)) + assert.Empty(t, values) values = c.PostFormArray("foo") - assert.Equal(t, 1, len(values)) + assert.Len(t, values, 1) assert.Equal(t, "bar", values[0]) dicts, ok := c.GetPostFormMap("names") @@ -661,14 +656,14 @@ func TestContextPostFormMultipart(t *testing.T) { dicts, ok = c.GetPostFormMap("nokey") assert.False(t, ok) - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) dicts = c.PostFormMap("names") assert.Equal(t, "thinkerou", dicts["a"]) assert.Equal(t, "tianou", dicts["b"]) dicts = c.PostFormMap("nokey") - assert.Equal(t, 0, len(dicts)) + assert.Empty(t, dicts) } func TestContextSetCookie(t *testing.T) { @@ -693,7 +688,7 @@ func TestContextGetCookie(t *testing.T) { assert.Equal(t, "gin", cookie) _, err := c.Cookie("nokey") - assert.Error(t, err) + require.Error(t, err) } func TestContextBodyAllowedForStatus(t *testing.T) { @@ -798,7 +793,7 @@ func TestContextRenderNoContentAPIJSON(t *testing.T) { assert.Equal(t, http.StatusNoContent, w.Code) assert.Empty(t, w.Body.String()) - assert.Equal(t, w.Header().Get("Content-Type"), "application/vnd.api+json") + assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } // Tests that the response is serialized as JSON @@ -1160,7 +1155,7 @@ func TestContextRenderProtoBuf(t *testing.T) { c.ProtoBuf(http.StatusCreated, data) protoData, err := proto.Marshal(data) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, string(protoData), w.Body.String()) @@ -1321,7 +1316,7 @@ func TestContextNegotiationNotSupport(t *testing.T) { }) assert.Equal(t, http.StatusNotAcceptable, w.Code) - assert.Equal(t, c.index, abortIndex) + assert.Equal(t, abortIndex, c.index) assert.True(t, c.IsAborted()) } @@ -1349,23 +1344,23 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "*/*") - assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") - assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") - assert.Equal(t, c.NegotiateFormat("application/*"), "application/*") - assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON) - assert.Equal(t, c.NegotiateFormat(MIMEXML), MIMEXML) - assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) + assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) + assert.Equal(t, "text/*", c.NegotiateFormat("text/*")) + assert.Equal(t, "application/*", c.NegotiateFormat("application/*")) + assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) + assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML)) + assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) c, _ = CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) c.Request.Header.Add("Accept", "text/*") - assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") - assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") - assert.Equal(t, c.NegotiateFormat("application/*"), "") - assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") - assert.Equal(t, c.NegotiateFormat(MIMEXML), "") - assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) + assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) + assert.Equal(t, "text/*", c.NegotiateFormat("text/*")) + assert.Equal(t, "", c.NegotiateFormat("application/*")) + assert.Equal(t, "", c.NegotiateFormat(MIMEJSON)) + assert.Equal(t, "", c.NegotiateFormat(MIMEXML)) + assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) } func TestContextNegotiationFormatCustom(t *testing.T) { @@ -1444,7 +1439,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) { buf := new(bytes.Buffer) _, err := buf.ReadFrom(w.Body) - assert.NoError(t, err) + require.NoError(t, err) jsonStringBody := buf.String() assert.Equal(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody) } @@ -1669,7 +1664,7 @@ func TestContextAutoBindJSON(t *testing.T) { Foo string `json:"foo"` Bar string `json:"bar"` } - assert.NoError(t, c.Bind(&obj)) + require.NoError(t, c.Bind(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Empty(t, c.Errors) @@ -1686,7 +1681,7 @@ func TestContextBindWithJSON(t *testing.T) { Foo string `json:"foo"` Bar string `json:"bar"` } - assert.NoError(t, c.BindJSON(&obj)) + require.NoError(t, c.BindJSON(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1707,7 +1702,7 @@ func TestContextBindWithXML(t *testing.T) { Foo string `xml:"foo"` Bar string `xml:"bar"` } - assert.NoError(t, c.BindXML(&obj)) + require.NoError(t, c.BindXML(&obj)) assert.Equal(t, "FOO", obj.Foo) assert.Equal(t, "BAR", obj.Bar) assert.Equal(t, 0, w.Body.Len()) @@ -1722,7 +1717,7 @@ func TestContextBindPlain(t *testing.T) { var s string - assert.NoError(t, c.BindPlain(&s)) + require.NoError(t, c.BindPlain(&s)) assert.Equal(t, "test string", s) assert.Equal(t, 0, w.Body.Len()) @@ -1732,7 +1727,7 @@ func TestContextBindPlain(t *testing.T) { var bs []byte - assert.NoError(t, c.BindPlain(&bs)) + require.NoError(t, c.BindPlain(&bs)) assert.Equal(t, []byte("test []byte"), bs) assert.Equal(t, 0, w.Body.Len()) } @@ -1752,7 +1747,7 @@ func TestContextBindHeader(t *testing.T) { Limit int `header:"limit"` } - assert.NoError(t, c.BindHeader(&testHeader)) + require.NoError(t, c.BindHeader(&testHeader)) assert.Equal(t, 8000, testHeader.Rate) assert.Equal(t, "music", testHeader.Domain) assert.Equal(t, 1000, testHeader.Limit) @@ -1769,7 +1764,7 @@ func TestContextBindWithQuery(t *testing.T) { Foo string `form:"foo"` Bar string `form:"bar"` } - assert.NoError(t, c.BindQuery(&obj)) + require.NoError(t, c.BindQuery(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1786,7 +1781,7 @@ func TestContextBindWithYAML(t *testing.T) { Foo string `yaml:"foo"` Bar string `yaml:"bar"` } - assert.NoError(t, c.BindYAML(&obj)) + require.NoError(t, c.BindYAML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1803,7 +1798,7 @@ func TestContextBindWithTOML(t *testing.T) { Foo string `toml:"foo"` Bar string `toml:"bar"` } - assert.NoError(t, c.BindTOML(&obj)) + require.NoError(t, c.BindTOML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1821,7 +1816,7 @@ func TestContextBadAutoBind(t *testing.T) { } assert.False(t, c.IsAborted()) - assert.Error(t, c.Bind(&obj)) + require.Error(t, c.Bind(&obj)) c.Writer.WriteHeaderNow() assert.Empty(t, obj.Bar) @@ -1839,7 +1834,7 @@ func TestContextAutoShouldBindJSON(t *testing.T) { Foo string `json:"foo"` Bar string `json:"bar"` } - assert.NoError(t, c.ShouldBind(&obj)) + require.NoError(t, c.ShouldBind(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Empty(t, c.Errors) @@ -1856,7 +1851,7 @@ func TestContextShouldBindWithJSON(t *testing.T) { Foo string `json:"foo"` Bar string `json:"bar"` } - assert.NoError(t, c.ShouldBindJSON(&obj)) + require.NoError(t, c.ShouldBindJSON(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1877,7 +1872,7 @@ func TestContextShouldBindWithXML(t *testing.T) { Foo string `xml:"foo"` Bar string `xml:"bar"` } - assert.NoError(t, c.ShouldBindXML(&obj)) + require.NoError(t, c.ShouldBindXML(&obj)) assert.Equal(t, "FOO", obj.Foo) assert.Equal(t, "BAR", obj.Bar) assert.Equal(t, 0, w.Body.Len()) @@ -1892,7 +1887,7 @@ func TestContextShouldBindPlain(t *testing.T) { var s string - assert.NoError(t, c.ShouldBindPlain(&s)) + require.NoError(t, c.ShouldBindPlain(&s)) assert.Equal(t, "test string", s) assert.Equal(t, 0, w.Body.Len()) // []byte @@ -1902,7 +1897,7 @@ func TestContextShouldBindPlain(t *testing.T) { var bs []byte - assert.NoError(t, c.ShouldBindPlain(&bs)) + require.NoError(t, c.ShouldBindPlain(&bs)) assert.Equal(t, []byte("test []byte"), bs) assert.Equal(t, 0, w.Body.Len()) } @@ -1922,7 +1917,7 @@ func TestContextShouldBindHeader(t *testing.T) { Limit int `header:"limit"` } - assert.NoError(t, c.ShouldBindHeader(&testHeader)) + require.NoError(t, c.ShouldBindHeader(&testHeader)) assert.Equal(t, 8000, testHeader.Rate) assert.Equal(t, "music", testHeader.Domain) assert.Equal(t, 1000, testHeader.Limit) @@ -1941,7 +1936,7 @@ func TestContextShouldBindWithQuery(t *testing.T) { Foo1 string `form:"Foo"` Bar1 string `form:"Bar"` } - assert.NoError(t, c.ShouldBindQuery(&obj)) + require.NoError(t, c.ShouldBindQuery(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "foo1", obj.Bar1) @@ -1960,7 +1955,7 @@ func TestContextShouldBindWithYAML(t *testing.T) { Foo string `yaml:"foo"` Bar string `yaml:"bar"` } - assert.NoError(t, c.ShouldBindYAML(&obj)) + require.NoError(t, c.ShouldBindYAML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1977,7 +1972,7 @@ func TestContextShouldBindWithTOML(t *testing.T) { Foo string `toml:"foo"` Bar string `toml:"bar"` } - assert.NoError(t, c.ShouldBindTOML(&obj)) + require.NoError(t, c.ShouldBindTOML(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) @@ -1995,7 +1990,7 @@ func TestContextBadAutoShouldBind(t *testing.T) { } assert.False(t, c.IsAborted()) - assert.Error(t, c.ShouldBind(&obj)) + require.Error(t, c.ShouldBind(&obj)) assert.Empty(t, obj.Bar) assert.Empty(t, obj.Foo) @@ -2056,10 +2051,10 @@ func TestContextShouldBindBodyWith(t *testing.T) { // When it binds to typeA and typeB, it finds the body is // not typeB but typeA. objA := typeA{} - assert.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + require.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) assert.Equal(t, typeA{"FOO"}, objA) objB := typeB{} - assert.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + require.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) assert.NotEqual(t, typeB{"BAR"}, objB) } // bodyB to typeA and typeB @@ -2072,10 +2067,10 @@ func TestContextShouldBindBodyWith(t *testing.T) { "POST", "http://example.com", bytes.NewBufferString(tt.bodyB), ) objA := typeA{} - assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + require.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) assert.NotEqual(t, typeA{"FOO"}, objA) objB := typeB{} - assert.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + require.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) assert.Equal(t, typeB{"BAR"}, objB) } } @@ -2124,22 +2119,22 @@ func TestContextShouldBindBodyWithJSON(t *testing.T) { objJSON := typeJSON{} if tt.bindingBody == binding.JSON { - assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{"FOO"}, objJSON) } if tt.bindingBody == binding.XML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } if tt.bindingBody == binding.YAML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } if tt.bindingBody == binding.TOML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } } @@ -2188,22 +2183,22 @@ func TestContextShouldBindBodyWithXML(t *testing.T) { objXML := typeXML{} if tt.bindingBody == binding.JSON { - assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + require.Error(t, c.ShouldBindBodyWithXML(&objXML)) assert.Equal(t, typeXML{}, objXML) } if tt.bindingBody == binding.XML { - assert.NoError(t, c.ShouldBindBodyWithXML(&objXML)) + require.NoError(t, c.ShouldBindBodyWithXML(&objXML)) assert.Equal(t, typeXML{"FOO"}, objXML) } if tt.bindingBody == binding.YAML { - assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + require.Error(t, c.ShouldBindBodyWithXML(&objXML)) assert.Equal(t, typeXML{}, objXML) } if tt.bindingBody == binding.TOML { - assert.Error(t, c.ShouldBindBodyWithXML(&objXML)) + require.Error(t, c.ShouldBindBodyWithXML(&objXML)) assert.Equal(t, typeXML{}, objXML) } } @@ -2253,22 +2248,22 @@ func TestContextShouldBindBodyWithYAML(t *testing.T) { // YAML belongs to a super collection of JSON, so JSON can be parsed by YAML if tt.bindingBody == binding.JSON { - assert.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) + require.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) assert.Equal(t, typeYAML{"FOO"}, objYAML) } if tt.bindingBody == binding.XML { - assert.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) + require.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) assert.Equal(t, typeYAML{}, objYAML) } if tt.bindingBody == binding.YAML { - assert.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) + require.NoError(t, c.ShouldBindBodyWithYAML(&objYAML)) assert.Equal(t, typeYAML{"FOO"}, objYAML) } if tt.bindingBody == binding.TOML { - assert.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) + require.Error(t, c.ShouldBindBodyWithYAML(&objYAML)) assert.Equal(t, typeYAML{}, objYAML) } } @@ -2317,22 +2312,22 @@ func TestContextShouldBindBodyWithTOML(t *testing.T) { objTOML := typeTOML{} if tt.bindingBody == binding.JSON { - assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + require.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) assert.Equal(t, typeTOML{}, objTOML) } if tt.bindingBody == binding.XML { - assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + require.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) assert.Equal(t, typeTOML{}, objTOML) } if tt.bindingBody == binding.YAML { - assert.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) + require.Error(t, c.ShouldBindBodyWithTOML(&objTOML)) assert.Equal(t, typeTOML{}, objTOML) } if tt.bindingBody == binding.TOML { - assert.NoError(t, c.ShouldBindBodyWithTOML(&objTOML)) + require.NoError(t, c.ShouldBindBodyWithTOML(&objTOML)) assert.Equal(t, typeTOML{"FOO"}, objTOML) } } @@ -2387,27 +2382,27 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { if tt.bindingBody == binding.Plain { body := "" - assert.NoError(t, c.ShouldBindBodyWithPlain(&body)) - assert.Equal(t, body, "foo=FOO") + require.NoError(t, c.ShouldBindBodyWithPlain(&body)) + assert.Equal(t, "foo=FOO", body) } if tt.bindingBody == binding.JSON { - assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.NoError(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{"FOO"}, objJSON) } if tt.bindingBody == binding.XML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } if tt.bindingBody == binding.YAML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } if tt.bindingBody == binding.TOML { - assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) + require.Error(t, c.ShouldBindBodyWithJSON(&objJSON)) assert.Equal(t, typeJSON{}, objJSON) } } @@ -2416,10 +2411,10 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) - assert.NoError(t, c.Err()) + require.NoError(t, c.Err()) assert.Nil(t, c.Done()) ti, ok := c.Deadline() - assert.Equal(t, ti, time.Time{}) + assert.Equal(t, time.Time{}, ti) assert.False(t, ok) assert.Equal(t, c.Value(ContextRequestKey), c.Request) assert.Equal(t, c.Value(ContextKey), c) @@ -2468,7 +2463,7 @@ func TestContextGetRawData(t *testing.T) { c.Request.Header.Add("Content-Type", MIMEPOSTForm) data, err := c.GetRawData() - assert.Nil(t, err) + require.NoError(t, err) assert.Equal(t, "Fetch binary post data", string(data)) } @@ -2539,7 +2534,7 @@ func TestContextStream(t *testing.T) { }() _, err := w.Write([]byte("test")) - assert.NoError(t, err) + require.NoError(t, err) return stopStream }) @@ -2557,7 +2552,7 @@ func TestContextStreamWithClientGone(t *testing.T) { }() _, err := writer.Write([]byte("test")) - assert.NoError(t, err) + require.NoError(t, err) return true }) @@ -2685,7 +2680,7 @@ func TestContextWithFallbackErrFromRequestContext(t *testing.T) { // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true - assert.Nil(t, c.Err()) + require.NoError(t, c.Err()) c2, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag @@ -2834,7 +2829,7 @@ func TestContextAddParam(t *testing.T) { c.AddParam(id, value) v, ok := c.Params.Get(id) - assert.Equal(t, ok, true) + assert.True(t, ok) assert.Equal(t, value, v) } diff --git a/debug_test.go b/debug_test.go index 1e57668..edf4bb1 100644 --- a/debug_test.go +++ b/debug_test.go @@ -17,6 +17,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // TODO @@ -154,13 +155,13 @@ func TestGetMinVer(t *testing.T) { var m uint64 var e error _, e = getMinVer("go1") - assert.NotNil(t, e) + require.Error(t, e) m, e = getMinVer("go1.1") assert.Equal(t, uint64(1), m) - assert.Nil(t, e) + require.NoError(t, e) m, e = getMinVer("go1.1.1") - assert.Nil(t, e) + require.NoError(t, e) assert.Equal(t, uint64(1), m) _, e = getMinVer("go1.1.1.1") - assert.NotNil(t, e) + require.Error(t, e) } diff --git a/errors_test.go b/errors_test.go index f77a634..72a3699 100644 --- a/errors_test.go +++ b/errors_test.go @@ -11,6 +11,7 @@ import ( "github.com/gin-gonic/gin/internal/json" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestError(t *testing.T) { @@ -122,7 +123,7 @@ func TestErrorUnwrap(t *testing.T) { }) // check that 'errors.Is()' and 'errors.As()' behave as expected : - assert.True(t, errors.Is(err, innerErr)) + require.ErrorIs(t, err, innerErr) var testErr TestErr - assert.True(t, errors.As(err, &testErr)) + require.ErrorAs(t, err, &testErr) } diff --git a/fs_test.go b/fs_test.go index a1690cd..167ac1a 100644 --- a/fs_test.go +++ b/fs_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type mockFileSystem struct { @@ -28,7 +29,7 @@ func TestOnlyFilesFS_Open(t *testing.T) { file, err := fs.Open("foo") - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, testFile, file.(neutralizedReaddirFile).File) } @@ -43,7 +44,7 @@ func TestOnlyFilesFS_Open_err(t *testing.T) { file, err := fs.Open("foo") - assert.ErrorIs(t, err, testError) + require.ErrorIs(t, err, testError) assert.Nil(t, file) } @@ -52,7 +53,7 @@ func Test_neuteredReaddirFile_Readdir(t *testing.T) { res, err := n.Readdir(0) - assert.NoError(t, err) + require.NoError(t, err) assert.Nil(t, res) } diff --git a/gin_integration_test.go b/gin_integration_test.go index 5398271..3082bc2 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // params[0]=url example:http://127.0.0.1:8080/index (cannot be empty) @@ -40,11 +41,11 @@ func testRequest(t *testing.T, params ...string) { client := &http.Client{Transport: tr} resp, err := client.Get(params[0]) - assert.NoError(t, err) + require.NoError(t, err) defer resp.Body.Close() body, ioerr := io.ReadAll(resp.Body) - assert.NoError(t, ioerr) + require.NoError(t, ioerr) var responseStatus = "200 OK" if len(params) > 1 && params[1] != "" { @@ -73,13 +74,13 @@ func TestRunEmpty(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.Run(":8080")) + require.Error(t, router.Run(":8080")) testRequest(t, "http://localhost:8080/example") } func TestBadTrustedCIDRs(t *testing.T) { router := New() - assert.Error(t, router.SetTrustedProxies([]string{"hello/world"})) + require.Error(t, router.SetTrustedProxies([]string{"hello/world"})) } /* legacy tests @@ -87,7 +88,7 @@ func TestBadTrustedCIDRsForRun(t *testing.T) { os.Setenv("PORT", "") router := New() router.TrustedProxies = []string{"hello/world"} - assert.Error(t, router.Run(":8080")) + require.Error(t, router.Run(":8080")) } func TestBadTrustedCIDRsForRunUnix(t *testing.T) { @@ -100,7 +101,7 @@ func TestBadTrustedCIDRsForRunUnix(t *testing.T) { go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - assert.Error(t, router.RunUnix(unixTestSocket)) + require.Error(t, router.RunUnix(unixTestSocket)) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete @@ -112,15 +113,15 @@ func TestBadTrustedCIDRsForRunFd(t *testing.T) { router.TrustedProxies = []string{"hello/world"} addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) socketFile, err := listener.File() - assert.NoError(t, err) + require.NoError(t, err) go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - assert.Error(t, router.RunFd(int(socketFile.Fd()))) + require.Error(t, router.RunFd(int(socketFile.Fd()))) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete @@ -132,12 +133,12 @@ func TestBadTrustedCIDRsForRunListener(t *testing.T) { router.TrustedProxies = []string{"hello/world"} addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - assert.Error(t, router.RunListener(listener)) + require.Error(t, router.RunListener(listener)) }() // have to wait for the goroutine to start and run the server // otherwise the main thread will complete @@ -148,7 +149,7 @@ func TestBadTrustedCIDRsForRunTLS(t *testing.T) { os.Setenv("PORT", "") router := New() router.TrustedProxies = []string{"hello/world"} - assert.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + require.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) } */ @@ -164,7 +165,7 @@ func TestRunTLS(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + require.Error(t, router.RunTLS(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) testRequest(t, "https://localhost:8443/example") } @@ -201,7 +202,7 @@ func TestPusher(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + require.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) testRequest(t, "https://localhost:8449/pusher") } @@ -216,14 +217,14 @@ func TestRunEmptyWithEnv(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.Run(":3123")) + require.Error(t, router.Run(":3123")) testRequest(t, "http://localhost:3123/example") } func TestRunTooMuchParams(t *testing.T) { router := New() assert.Panics(t, func() { - assert.NoError(t, router.Run("2", "2")) + require.NoError(t, router.Run("2", "2")) }) } @@ -237,7 +238,7 @@ func TestRunWithPort(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.Run(":5150")) + require.Error(t, router.Run(":5150")) testRequest(t, "http://localhost:5150/example") } @@ -257,7 +258,7 @@ func TestUnixSocket(t *testing.T) { time.Sleep(5 * time.Millisecond) c, err := net.Dial("unix", unixTestSocket) - assert.NoError(t, err) + require.NoError(t, err) fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) @@ -271,7 +272,7 @@ func TestUnixSocket(t *testing.T) { func TestBadUnixSocket(t *testing.T) { router := New() - assert.Error(t, router.RunUnix("#/tmp/unix_unit_test")) + require.Error(t, router.RunUnix("#/tmp/unix_unit_test")) } func TestRunQUIC(t *testing.T) { @@ -286,7 +287,7 @@ func TestRunQUIC(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - assert.Error(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + require.Error(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) testRequest(t, "https://localhost:8443/example") } @@ -294,15 +295,15 @@ func TestFileDescriptor(t *testing.T) { router := New() addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) socketFile, err := listener.File() if isWindows() { // not supported by windows, it is unimplemented now - assert.Error(t, err) + require.Error(t, err) } else { - assert.NoError(t, err) + require.NoError(t, err) } if socketFile == nil { @@ -318,7 +319,7 @@ func TestFileDescriptor(t *testing.T) { time.Sleep(5 * time.Millisecond) c, err := net.Dial("tcp", listener.Addr().String()) - assert.NoError(t, err) + require.NoError(t, err) fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) @@ -332,15 +333,15 @@ func TestFileDescriptor(t *testing.T) { func TestBadFileDescriptor(t *testing.T) { router := New() - assert.Error(t, router.RunFd(0)) + require.Error(t, router.RunFd(0)) } func TestListener(t *testing.T) { router := New() addr, err := net.ResolveTCPAddr("tcp", "localhost:0") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) go func() { router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) assert.NoError(t, router.RunListener(listener)) @@ -350,7 +351,7 @@ func TestListener(t *testing.T) { time.Sleep(5 * time.Millisecond) c, err := net.Dial("tcp", listener.Addr().String()) - assert.NoError(t, err) + require.NoError(t, err) fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") scanner := bufio.NewScanner(c) @@ -365,11 +366,11 @@ func TestListener(t *testing.T) { func TestBadListener(t *testing.T) { router := New() addr, err := net.ResolveTCPAddr("tcp", "localhost:10086") - assert.NoError(t, err) + require.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) - assert.NoError(t, err) + require.NoError(t, err) listener.Close() - assert.Error(t, router.RunListener(listener)) + require.Error(t, router.RunListener(listener)) } func TestWithHttptestWithAutoSelectedPort(t *testing.T) { @@ -395,7 +396,14 @@ func TestConcurrentHandleContext(t *testing.T) { wg.Add(iterations) for i := 0; i < iterations; i++ { go func() { - testGetRequestHandler(t, router, "/") + req, err := http.NewRequest(http.MethodGet, "/", nil) + assert.NoError(t, err) + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, "it worked", w.Body.String(), "resp body should match") + assert.Equal(t, 200, w.Code, "should get a 200") wg.Done() }() } @@ -417,17 +425,6 @@ func TestConcurrentHandleContext(t *testing.T) { // testRequest(t, "http://localhost:8033/example") // } -func testGetRequestHandler(t *testing.T, h http.Handler, url string) { - req, err := http.NewRequest(http.MethodGet, url, nil) - assert.NoError(t, err) - - w := httptest.NewRecorder() - h.ServeHTTP(w, req) - - assert.Equal(t, "it worked", w.Body.String(), "resp body should match") - assert.Equal(t, 200, w.Code, "should get a 200") -} - func TestTreeRunDynamicRouting(t *testing.T) { router := New() router.GET("/aa/*xx", func(c *Context) { c.String(http.StatusOK, "/aa/*xx") }) diff --git a/gin_test.go b/gin_test.go index db70a8c..719f63e 100644 --- a/gin_test.go +++ b/gin_test.go @@ -20,6 +20,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/net/http2" ) @@ -547,10 +548,10 @@ func TestEngineHandleContextManyReEntries(t *testing.T) { r.GET("/:count", func(c *Context) { countStr := c.Param("count") count, err := strconv.Atoi(countStr) - assert.NoError(t, err) + require.NoError(t, err) n, err := c.Writer.Write([]byte(".")) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, 1, n) switch { @@ -580,7 +581,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")} err := r.SetTrustedProxies([]string{"0.0.0.0/0"}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -588,7 +589,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { { err := r.SetTrustedProxies([]string{"192.168.1.33/33"}) - assert.Error(t, err) + require.Error(t, err) } // valid ipv4 address @@ -597,7 +598,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { err := r.SetTrustedProxies([]string{"192.168.1.33"}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -605,7 +606,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { { err := r.SetTrustedProxies([]string{"192.168.1.256"}) - assert.Error(t, err) + require.Error(t, err) } // valid ipv6 address @@ -613,7 +614,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")} err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -621,7 +622,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { { err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"}) - assert.Error(t, err) + require.Error(t, err) } // valid ipv6 cidr @@ -629,7 +630,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")} err := r.SetTrustedProxies([]string{"::/0"}) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -637,7 +638,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { { err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"}) - assert.Error(t, err) + require.Error(t, err) } // valid combination @@ -653,7 +654,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { "172.16.0.1", }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs) } @@ -665,7 +666,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { "172.16.0.256", }) - assert.Error(t, err) + require.Error(t, err) } // nil value @@ -673,7 +674,7 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) { err := r.SetTrustedProxies(nil) assert.Nil(t, r.trustedCIDRs) - assert.Nil(t, err) + require.NoError(t, err) } } diff --git a/githubapi_test.go b/githubapi_test.go index 9276bed..6d34878 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -14,6 +14,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type route struct { @@ -295,9 +296,9 @@ func TestShouldBindUri(t *testing.T) { } router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person - assert.NoError(t, c.ShouldBindUri(&person)) - assert.True(t, person.Name != "") - assert.True(t, person.ID != "") + require.NoError(t, c.ShouldBindUri(&person)) + assert.NotEqual(t, "", person.Name) + assert.NotEqual(t, "", person.ID) c.String(http.StatusOK, "ShouldBindUri test OK") }) @@ -317,9 +318,9 @@ func TestBindUri(t *testing.T) { } router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) { var person Person - assert.NoError(t, c.BindUri(&person)) - assert.True(t, person.Name != "") - assert.True(t, person.ID != "") + require.NoError(t, c.BindUri(&person)) + assert.NotEqual(t, "", person.Name) + assert.NotEqual(t, "", person.ID) c.String(http.StatusOK, "BindUri test OK") }) @@ -338,7 +339,7 @@ func TestBindUriError(t *testing.T) { } router.Handle(http.MethodGet, "/new/rest/:num", func(c *Context) { var m Member - assert.Error(t, c.BindUri(&m)) + require.Error(t, c.BindUri(&m)) }) path1, _ := exampleFromPath("/new/rest/:num") diff --git a/logger_test.go b/logger_test.go index 6c1814d..b05df74 100644 --- a/logger_test.go +++ b/logger_test.go @@ -329,13 +329,13 @@ func TestIsOutputColor(t *testing.T) { } consoleColorMode = autoColor - assert.Equal(t, true, p.IsOutputColor()) + assert.True(t, p.IsOutputColor()) ForceConsoleColor() - assert.Equal(t, true, p.IsOutputColor()) + assert.True(t, p.IsOutputColor()) DisableConsoleColor() - assert.Equal(t, false, p.IsOutputColor()) + assert.False(t, p.IsOutputColor()) // test with isTerm flag false. p = LogFormatterParams{ @@ -343,13 +343,13 @@ func TestIsOutputColor(t *testing.T) { } consoleColorMode = autoColor - assert.Equal(t, false, p.IsOutputColor()) + assert.False(t, p.IsOutputColor()) ForceConsoleColor() - assert.Equal(t, true, p.IsOutputColor()) + assert.True(t, p.IsOutputColor()) DisableConsoleColor() - assert.Equal(t, false, p.IsOutputColor()) + assert.False(t, p.IsOutputColor()) // reset console color mode. consoleColorMode = autoColor diff --git a/path_test.go b/path_test.go index 864302f..2269b78 100644 --- a/path_test.go +++ b/path_test.go @@ -87,7 +87,7 @@ func TestPathCleanMallocs(t *testing.T) { for _, test := range cleanTests { allocs := testing.AllocsPerRun(100, func() { cleanPath(test.result) }) - assert.EqualValues(t, allocs, 0) + assert.InDelta(t, 0, allocs, 0.01) } } diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go index db4b71e..579897c 100644 --- a/render/render_msgpack_test.go +++ b/render/render_msgpack_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/ugorji/go/codec" ) @@ -29,7 +30,7 @@ func TestRenderMsgPack(t *testing.T) { err := (MsgPack{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) h := new(codec.MsgpackHandle) assert.NotNil(t, h) @@ -37,7 +38,7 @@ func TestRenderMsgPack(t *testing.T) { assert.NotNil(t, buf) err = codec.NewEncoder(buf, h).Encode(data) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, w.Body.String(), buf.String()) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/render/render_test.go b/render/render_test.go index 145f131..27a5065 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -18,6 +18,7 @@ import ( "github.com/gin-gonic/gin/internal/json" testdata "github.com/gin-gonic/gin/testdata/protoexample" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) @@ -36,7 +37,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -46,7 +47,7 @@ func TestRenderJSONError(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - assert.Error(t, (JSON{data}).Render(w)) + require.Error(t, (JSON{data}).Render(w)) } func TestRenderIndentedJSON(t *testing.T) { @@ -58,7 +59,7 @@ func TestRenderIndentedJSON(t *testing.T) { err := (IndentedJSON{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -69,7 +70,7 @@ func TestRenderIndentedJSONPanics(t *testing.T) { // json: unsupported type: chan int err := (IndentedJSON{data}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderSecureJSON(t *testing.T) { @@ -83,7 +84,7 @@ func TestRenderSecureJSON(t *testing.T) { err1 := (SecureJSON{"while(1);", data}).Render(w1) - assert.NoError(t, err1) + require.NoError(t, err1) assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) @@ -95,7 +96,7 @@ func TestRenderSecureJSON(t *testing.T) { }} err2 := (SecureJSON{"while(1);", datas}).Render(w2) - assert.NoError(t, err2) + require.NoError(t, err2) assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type")) } @@ -106,7 +107,7 @@ func TestRenderSecureJSONFail(t *testing.T) { // json: unsupported type: chan int err := (SecureJSON{"while(1);", data}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderJsonpJSON(t *testing.T) { @@ -120,7 +121,7 @@ func TestRenderJsonpJSON(t *testing.T) { err1 := (JsonpJSON{"x", data}).Render(w1) - assert.NoError(t, err1) + require.NoError(t, err1) assert.Equal(t, "x({\"foo\":\"bar\"});", w1.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) @@ -132,7 +133,7 @@ func TestRenderJsonpJSON(t *testing.T) { }} err2 := (JsonpJSON{"x", datas}).Render(w2) - assert.NoError(t, err2) + require.NoError(t, err2) assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}]);", w2.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) } @@ -191,7 +192,7 @@ func TestRenderJsonpJSONError2(t *testing.T) { assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) e := (JsonpJSON{"", data}).Render(w) - assert.NoError(t, e) + require.NoError(t, e) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) @@ -203,7 +204,7 @@ func TestRenderJsonpJSONFail(t *testing.T) { // json: unsupported type: chan int err := (JsonpJSON{"x", data}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderAsciiJSON(t *testing.T) { @@ -215,7 +216,7 @@ func TestRenderAsciiJSON(t *testing.T) { err := (AsciiJSON{data1}).Render(w1) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) assert.Equal(t, "application/json", w1.Header().Get("Content-Type")) @@ -223,7 +224,7 @@ func TestRenderAsciiJSON(t *testing.T) { data2 := 3.1415926 err = (AsciiJSON{data2}).Render(w2) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "3.1415926", w2.Body.String()) } @@ -232,7 +233,7 @@ func TestRenderAsciiJSONFail(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - assert.Error(t, (AsciiJSON{data}).Render(w)) + require.Error(t, (AsciiJSON{data}).Render(w)) } func TestRenderPureJSON(t *testing.T) { @@ -242,7 +243,7 @@ func TestRenderPureJSON(t *testing.T) { "html": "", } err := (PureJSON{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -283,7 +284,7 @@ b: assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) err := (YAML{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String()) assert.Equal(t, "application/yaml; charset=utf-8", w.Header().Get("Content-Type")) } @@ -298,7 +299,7 @@ func (ft *fail) MarshalYAML() (any, error) { func TestRenderYAMLFail(t *testing.T) { w := httptest.NewRecorder() err := (YAML{&fail{}}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderTOML(t *testing.T) { @@ -311,7 +312,7 @@ func TestRenderTOML(t *testing.T) { assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) err := (TOML{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "foo = 'bar'\nhtml = ''\n", w.Body.String()) assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type")) } @@ -319,7 +320,7 @@ func TestRenderTOML(t *testing.T) { func TestRenderTOMLFail(t *testing.T) { w := httptest.NewRecorder() err := (TOML{net.IPv4bcast}).Render(w) - assert.Error(t, err) + require.Error(t, err) } // test Protobuf rendering @@ -334,12 +335,12 @@ func TestRenderProtoBuf(t *testing.T) { (ProtoBuf{data}).WriteContentType(w) protoData, err := proto.Marshal(data) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) err = (ProtoBuf{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) } @@ -348,7 +349,7 @@ func TestRenderProtoBufFail(t *testing.T) { w := httptest.NewRecorder() data := &testdata.Test{} err := (ProtoBuf{data}).Render(w) - assert.Error(t, err) + require.Error(t, err) } func TestRenderXML(t *testing.T) { @@ -362,14 +363,14 @@ func TestRenderXML(t *testing.T) { err := (XML{data}).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "bar", w.Body.String()) assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderRedirect(t *testing.T) { req, err := http.NewRequest("GET", "/test-redirect", nil) - assert.NoError(t, err) + require.NoError(t, err) data1 := Redirect{ Code: http.StatusMovedPermanently, @@ -379,7 +380,7 @@ func TestRenderRedirect(t *testing.T) { w := httptest.NewRecorder() err = data1.Render(w) - assert.NoError(t, err) + require.NoError(t, err) data2 := Redirect{ Code: http.StatusOK, @@ -390,7 +391,7 @@ func TestRenderRedirect(t *testing.T) { w = httptest.NewRecorder() assert.PanicsWithValue(t, "Cannot redirect with status code 200", func() { err := data2.Render(w) - assert.NoError(t, err) + require.NoError(t, err) }) data3 := Redirect{ @@ -401,7 +402,7 @@ func TestRenderRedirect(t *testing.T) { w = httptest.NewRecorder() err = data3.Render(w) - assert.NoError(t, err) + require.NoError(t, err) // only improve coverage data2.WriteContentType(w) @@ -416,7 +417,7 @@ func TestRenderData(t *testing.T) { Data: data, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "#!PNG some raw data", w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) } @@ -435,7 +436,7 @@ func TestRenderString(t *testing.T) { Data: []any{"manu", 2}, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "hola manu 2", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } @@ -448,7 +449,7 @@ func TestRenderStringLenZero(t *testing.T) { Data: []any{}, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "hola %s %d", w.Body.String()) assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } @@ -464,7 +465,7 @@ func TestRenderHTMLTemplate(t *testing.T) { err := instance.Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } @@ -480,7 +481,7 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { err := instance.Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "Hello alexandernyquist", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } @@ -499,7 +500,7 @@ func TestRenderHTMLDebugFiles(t *testing.T) { err := instance.Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "

Hello thinkerou

", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } @@ -518,7 +519,7 @@ func TestRenderHTMLDebugGlob(t *testing.T) { err := instance.Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "

Hello thinkerou

", w.Body.String()) assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } @@ -548,7 +549,7 @@ func TestRenderReader(t *testing.T) { Headers: headers, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, body, w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length")) @@ -571,7 +572,7 @@ func TestRenderReaderNoContentLength(t *testing.T) { Headers: headers, }).Render(w) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, body, w.Body.String()) assert.Equal(t, "image/png", w.Header().Get("Content-Type")) assert.NotContains(t, "Content-Length", w.Header()) @@ -588,6 +589,6 @@ func TestRenderWriteError(t *testing.T) { ResponseRecorder: httptest.NewRecorder(), } err := r.Render(ew) - assert.NotNil(t, err) + require.Error(t, err) assert.Equal(t, `write "my-prefix:" error`, err.Error()) } diff --git a/response_writer_test.go b/response_writer_test.go index 964aa30..259b8fa 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // TODO @@ -95,13 +96,13 @@ func TestResponseWriterWrite(t *testing.T) { assert.Equal(t, http.StatusOK, w.Status()) assert.Equal(t, http.StatusOK, testWriter.Code) assert.Equal(t, "hola", testWriter.Body.String()) - assert.NoError(t, err) + require.NoError(t, err) n, err = w.Write([]byte(" adios")) assert.Equal(t, 6, n) assert.Equal(t, 10, w.Size()) assert.Equal(t, "hola adios", testWriter.Body.String()) - assert.NoError(t, err) + require.NoError(t, err) } func TestResponseWriterHijack(t *testing.T) { @@ -112,7 +113,7 @@ func TestResponseWriterHijack(t *testing.T) { assert.Panics(t, func() { _, _, err := w.Hijack() - assert.NoError(t, err) + require.NoError(t, err) }) assert.True(t, w.Written()) @@ -135,7 +136,7 @@ func TestResponseWriterFlush(t *testing.T) { // should return 500 resp, err := http.Get(testServer.URL) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) } diff --git a/routes_test.go b/routes_test.go index 73f393e..d6233b0 100644 --- a/routes_test.go +++ b/routes_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type header struct { @@ -386,7 +387,7 @@ func TestRouteStaticFile(t *testing.T) { } defer os.Remove(f.Name()) _, err = f.WriteString("Gin Web Framework") - assert.NoError(t, err) + require.NoError(t, err) f.Close() dir, filename := filepath.Split(f.Name()) @@ -421,7 +422,7 @@ func TestRouteStaticFileFS(t *testing.T) { } defer os.Remove(f.Name()) _, err = f.WriteString("Gin Web Framework") - assert.NoError(t, err) + require.NoError(t, err) f.Close() dir, filename := filepath.Split(f.Name()) @@ -484,7 +485,7 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, // else, Content-Type='text/x-go; charset=utf-8' assert.NotEqual(t, "", w.Header().Get("Content-Type")) - assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") + assert.NotEqual(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Last-Modified")) assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN")) } diff --git a/utils_test.go b/utils_test.go index 058ddb9..af08996 100644 --- a/utils_test.go +++ b/utils_test.go @@ -145,6 +145,6 @@ func TestMarshalXMLforH(t *testing.T) { } func TestIsASCII(t *testing.T) { - assert.Equal(t, isASCII("test"), true) - assert.Equal(t, isASCII("🧡💛💚💙💜"), false) + assert.True(t, isASCII("test")) + assert.False(t, isASCII("🧡💛💚💙💜")) } From cc4e11438cd6c0bcc632fe3492d3817dfa21c337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:34:34 +0800 Subject: [PATCH 882/912] chore(deps): bump golang.org/x/net from 0.25.0 to 0.27.0 (#4013) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.25.0 to 0.27.0. - [Commits](https://github.com/golang/net/compare/v0.25.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 30 ++++++++++++++++-------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 4937d2b..035c2de 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.43.1 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.25.0 + golang.org/x/net v0.27.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -39,10 +39,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.23.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect - golang.org/x/mod v0.11.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect ) diff --git a/go.sum b/go.sum index 44af4cc..55a2162 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -92,24 +92,26 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 3cb30679b5e3021db16c776ed7e70d380586e9ce Mon Sep 17 00:00:00 2001 From: Jo YoHan <37216082+slowhigh@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:16:30 +0900 Subject: [PATCH 883/912] feat(form): add array collection format in form binding (#3986) * feat(form): add array collection format in form binding * feat(form): add array collection format in form binding * test(form): fix test code for array collection format in form binding --- binding/form_mapping.go | 40 ++++++++++++++++++++++++++ binding/form_mapping_test.go | 39 ++++++++++++++++++++++++++ docs/doc.md | 54 ++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 33389b2..4a35866 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -182,6 +182,38 @@ func trySetCustom(val string, value reflect.Value) (isSet bool, err error) { return false, nil } +func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) { + cfTag := field.Tag.Get("collection_format") + if cfTag == "" || cfTag == "multi" { + return vs, nil + } + + var sep string + switch cfTag { + case "csv": + sep = "," + case "ssv": + sep = " " + case "tsv": + sep = "\t" + case "pipes": + sep = "|" + default: + return vs, fmt.Errorf("%s is not supported in the collection_format. (csv, ssv, pipes)", cfTag) + } + + totalLength := 0 + for _, v := range vs { + totalLength += strings.Count(v, sep) + 1 + } + newVs = make([]string, 0, totalLength) + for _, v := range vs { + newVs = append(newVs, strings.Split(v, sep)...) + } + + return newVs, nil +} + func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) { vs, ok := form[tagValue] if !ok && !opt.isDefaultExists { @@ -198,6 +230,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ return ok, err } + if vs, err = trySplit(vs, field); err != nil { + return false, err + } + return true, setSlice(vs, value, field) case reflect.Array: if !ok { @@ -208,6 +244,10 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ return ok, err } + if vs, err = trySplit(vs, field); err != nil { + return false, err + } + if len(vs) != value.Len() { return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) } diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 9ea0895..c6db033 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -264,6 +264,45 @@ func TestMappingArray(t *testing.T) { require.Error(t, err) } +func TestMappingCollectionFormat(t *testing.T) { + var s struct { + SliceMulti []int `form:"slice_multi" collection_format:"multi"` + SliceCsv []int `form:"slice_csv" collection_format:"csv"` + SliceSsv []int `form:"slice_ssv" collection_format:"ssv"` + SliceTsv []int `form:"slice_tsv" collection_format:"tsv"` + SlicePipes []int `form:"slice_pipes" collection_format:"pipes"` + ArrayMulti [2]int `form:"array_multi" collection_format:"multi"` + ArrayCsv [2]int `form:"array_csv" collection_format:"csv"` + ArraySsv [2]int `form:"array_ssv" collection_format:"ssv"` + ArrayTsv [2]int `form:"array_tsv" collection_format:"tsv"` + ArrayPipes [2]int `form:"array_pipes" collection_format:"pipes"` + } + err := mappingByPtr(&s, formSource{ + "slice_multi": {"1", "2"}, + "slice_csv": {"1,2"}, + "slice_ssv": {"1 2"}, + "slice_tsv": {"1 2"}, + "slice_pipes": {"1|2"}, + "array_multi": {"1", "2"}, + "array_csv": {"1,2"}, + "array_ssv": {"1 2"}, + "array_tsv": {"1 2"}, + "array_pipes": {"1|2"}, + }, "form") + require.NoError(t, err) + + assert.Equal(t, []int{1, 2}, s.SliceMulti) + assert.Equal(t, []int{1, 2}, s.SliceCsv) + assert.Equal(t, []int{1, 2}, s.SliceSsv) + assert.Equal(t, []int{1, 2}, s.SliceTsv) + assert.Equal(t, []int{1, 2}, s.SlicePipes) + assert.Equal(t, [2]int{1, 2}, s.ArrayMulti) + assert.Equal(t, [2]int{1, 2}, s.ArrayCsv) + assert.Equal(t, [2]int{1, 2}, s.ArraySsv) + assert.Equal(t, [2]int{1, 2}, s.ArrayTsv) + assert.Equal(t, [2]int{1, 2}, s.ArrayPipes) +} + func TestMappingStructField(t *testing.T) { var s struct { J struct { diff --git a/docs/doc.md b/docs/doc.md index 5136640..b76011f 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -26,6 +26,7 @@ - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Collection format for arrays](#collection-format-for-arrays) - [Bind Uri](#bind-uri) - [Bind custom unmarshaler](#bind-custom-unmarshaler) - [Bind Header](#bind-header) @@ -861,6 +862,59 @@ Test it with: curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" ``` +#### Collection format for arrays + +| Format | Description | Example | +| --------------- | --------------------------------------------------------- | ----------------------- | +| multi (default) | Multiple parameter instances rather than multiple values. | key=foo&key=bar&key=baz | +| csv | Comma-separated values. | foo,bar,baz | +| ssv | Space-separated values. | foo bar baz | +| tsv | Tab-separated values. | "foo\tbar\tbaz" | +| pipes | Pipe-separated values. | foo\|bar\|baz | + +```go +package main + +import ( + "log" + "time" + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Addresses []string `form:"addresses" collection_format:"csv"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` +} + +func main() { + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") +} +func startPage(c *gin.Context) { + var person Person + // If `GET`, only `Form` binding engine (`query`) used. + // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Addresses) + log.Println(person.Birthday) + log.Println(person.CreateTime) + log.Println(person.UnixTime) + } + c.String(200, "Success") +} +``` + +Test it with: +```sh +$ curl -X GET "localhost:8085/testing?name=appleboy&addresses=foo,bar&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" +``` + ### Bind Uri See the [detail information](https://github.com/gin-gonic/gin/issues/846). From 28e57f58b184b2305ace192e02496bb89f6fd8cb Mon Sep 17 00:00:00 2001 From: Ahmad Saeed Goda Date: Fri, 6 Sep 2024 08:21:19 +0300 Subject: [PATCH 884/912] fix(form): Set default value for form fields (#4047) - Use specified default value in struct tags when binding a request input to struct for validation, even if sent empty, not only when not sent at all. - Add string field to `TestMappingDefault` test case. - Add test case for not sent form field to default to the value specified via code. - Add test case for form field sent empty to default to the value specified via code. Fixes: How to apply default value if empty value provided by client during model binding? #4042, #13042df, #a41721a --- binding/form_mapping.go | 3 +++ binding/form_mapping_test.go | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 4a35866..a84536f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -261,6 +261,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ if len(vs) > 0 { val = vs[0] + if val == "" { + val = opt.defaultValue + } } if ok, err := trySetCustom(val, value); ok { return ok, err diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index c6db033..8cf7465 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -69,6 +69,7 @@ func TestMappingBaseTypes(t *testing.T) { func TestMappingDefault(t *testing.T) { var s struct { + Str string `form:",default=defaultVal"` Int int `form:",default=9"` Slice []int `form:",default=9"` Array [1]int `form:",default=9"` @@ -76,6 +77,7 @@ func TestMappingDefault(t *testing.T) { err := mappingByPtr(&s, formSource{}, "form") require.NoError(t, err) + assert.Equal(t, "defaultVal", s.Str) assert.Equal(t, 9, s.Int) assert.Equal(t, []int{9}, s.Slice) assert.Equal(t, [1]int{9}, s.Array) @@ -152,6 +154,24 @@ func TestMappingForm(t *testing.T) { assert.Equal(t, 6, s.F) } +func TestMappingFormFieldNotSent(t *testing.T) { + var s struct { + F string `form:"field,default=defVal"` + } + err := mapForm(&s, map[string][]string{}) + require.NoError(t, err) + assert.Equal(t, "defVal", s.F) +} + +func TestMappingFormWithEmptyToDefault(t *testing.T) { + var s struct { + F string `form:"field,default=DefVal"` + } + err := mapForm(&s, map[string][]string{"field": {""}}) + require.NoError(t, err) + assert.Equal(t, "DefVal", s.F) +} + func TestMapFormWithTag(t *testing.T) { var s struct { F int `externalTag:"field"` From f2c861a24f204f53dd6e6755b6d4efece7e373ea Mon Sep 17 00:00:00 2001 From: demouth <1133178+demouth@users.noreply.github.com> Date: Sun, 15 Sep 2024 09:54:23 +0900 Subject: [PATCH 885/912] docs: fix route group example code (#4020) --- docs/doc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/doc.md b/docs/doc.md index b76011f..875dd61 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -339,16 +339,16 @@ func main() { router := gin.Default() // Simple group: v1 - v1 := router.Group("/v1") { + v1 := router.Group("/v1") v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) } // Simple group: v2 - v2 := router.Group("/v2") { + v2 := router.Group("/v2") v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) From 9d7c0e9e1a301f417df9dc89a8cadc3bf9063db2 Mon Sep 17 00:00:00 2001 From: CC11001100 Date: Sun, 15 Sep 2024 08:58:59 +0800 Subject: [PATCH 886/912] feat(context): GetXxx added support for more go native types (#3633) --- context.go | 144 ++++++++++++++++++++++++++++++++++++++++++++- context_test.go | 152 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index baa4b0f..1f35651 100644 --- a/context.go +++ b/context.go @@ -315,7 +315,31 @@ func (c *Context) GetInt(key string) (i int) { return } -// GetInt64 returns the value associated with the key as an integer. +// GetInt8 returns the value associated with the key as an integer 8. +func (c *Context) GetInt8(key string) (i8 int8) { + if val, ok := c.Get(key); ok && val != nil { + i8, _ = val.(int8) + } + return +} + +// GetInt16 returns the value associated with the key as an integer 16. +func (c *Context) GetInt16(key string) (i16 int16) { + if val, ok := c.Get(key); ok && val != nil { + i16, _ = val.(int16) + } + return +} + +// GetInt32 returns the value associated with the key as an integer 32. +func (c *Context) GetInt32(key string) (i32 int32) { + if val, ok := c.Get(key); ok && val != nil { + i32, _ = val.(int32) + } + return +} + +// GetInt64 returns the value associated with the key as an integer 64. func (c *Context) GetInt64(key string) (i64 int64) { if val, ok := c.Get(key); ok && val != nil { i64, _ = val.(int64) @@ -331,7 +355,31 @@ func (c *Context) GetUint(key string) (ui uint) { return } -// GetUint64 returns the value associated with the key as an unsigned integer. +// GetUint8 returns the value associated with the key as an unsigned integer 8. +func (c *Context) GetUint8(key string) (ui8 uint8) { + if val, ok := c.Get(key); ok && val != nil { + ui8, _ = val.(uint8) + } + return +} + +// GetUint16 returns the value associated with the key as an unsigned integer 16. +func (c *Context) GetUint16(key string) (ui16 uint16) { + if val, ok := c.Get(key); ok && val != nil { + ui16, _ = val.(uint16) + } + return +} + +// GetUint32 returns the value associated with the key as an unsigned integer 32. +func (c *Context) GetUint32(key string) (ui32 uint32) { + if val, ok := c.Get(key); ok && val != nil { + ui32, _ = val.(uint32) + } + return +} + +// GetUint64 returns the value associated with the key as an unsigned integer 64. func (c *Context) GetUint64(key string) (ui64 uint64) { if val, ok := c.Get(key); ok && val != nil { ui64, _ = val.(uint64) @@ -339,6 +387,14 @@ func (c *Context) GetUint64(key string) (ui64 uint64) { return } +// GetFloat32 returns the value associated with the key as a float32. +func (c *Context) GetFloat32(key string) (f32 float32) { + if val, ok := c.Get(key); ok && val != nil { + f32, _ = val.(float32) + } + return +} + // GetFloat64 returns the value associated with the key as a float64. func (c *Context) GetFloat64(key string) (f64 float64) { if val, ok := c.Get(key); ok && val != nil { @@ -363,6 +419,90 @@ func (c *Context) GetDuration(key string) (d time.Duration) { return } +func (c *Context) GetIntSlice(key string) (is []int) { + if val, ok := c.Get(key); ok && val != nil { + is, _ = val.([]int) + } + return +} + +func (c *Context) GetInt8Slice(key string) (i8s []int8) { + if val, ok := c.Get(key); ok && val != nil { + i8s, _ = val.([]int8) + } + return +} + +func (c *Context) GetInt16Slice(key string) (i16s []int16) { + if val, ok := c.Get(key); ok && val != nil { + i16s, _ = val.([]int16) + } + return +} + +func (c *Context) GetInt32Slice(key string) (i32s []int32) { + if val, ok := c.Get(key); ok && val != nil { + i32s, _ = val.([]int32) + } + return +} + +func (c *Context) GetInt64Slice(key string) (i64s []int64) { + if val, ok := c.Get(key); ok && val != nil { + i64s, _ = val.([]int64) + } + return +} + +func (c *Context) GetUintSlice(key string) (uis []uint) { + if val, ok := c.Get(key); ok && val != nil { + uis, _ = val.([]uint) + } + return +} + +func (c *Context) GetUint8Slice(key string) (ui8s []uint8) { + if val, ok := c.Get(key); ok && val != nil { + ui8s, _ = val.([]uint8) + } + return +} + +func (c *Context) GetUint16Slice(key string) (ui16s []uint16) { + if val, ok := c.Get(key); ok && val != nil { + ui16s, _ = val.([]uint16) + } + return +} + +func (c *Context) GetUint32Slice(key string) (ui32s []uint32) { + if val, ok := c.Get(key); ok && val != nil { + ui32s, _ = val.([]uint32) + } + return +} + +func (c *Context) GetUint64Slice(key string) (ui64s []uint64) { + if val, ok := c.Get(key); ok && val != nil { + ui64s, _ = val.([]uint64) + } + return +} + +func (c *Context) GetFloat32Slice(key string) (f32s []float32) { + if val, ok := c.Get(key); ok && val != nil { + f32s, _ = val.([]float32) + } + return +} + +func (c *Context) GetFloat64Slice(key string) (f64s []float64) { + if val, ok := c.Get(key); ok && val != nil { + f64s, _ = val.([]float64) + } + return +} + // GetStringSlice returns the value associated with the key as a slice of strings. func (c *Context) GetStringSlice(key string) (ss []string) { if val, ok := c.Get(key); ok && val != nil { diff --git a/context_test.go b/context_test.go index 66190b3..211fbb1 100644 --- a/context_test.go +++ b/context_test.go @@ -252,6 +252,30 @@ func TestContextGetInt(t *testing.T) { assert.Equal(t, 1, c.GetInt("int")) } +func TestContextGetInt8(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int8" + value := int8(0x7F) + c.Set(key, value) + assert.Equal(t, value, c.GetInt8(key)) +} + +func TestContextGetInt16(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int16" + value := int16(0x7FFF) + c.Set(key, value) + assert.Equal(t, value, c.GetInt16(key)) +} + +func TestContextGetInt32(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int32" + value := int32(0x7FFFFFFF) + c.Set(key, value) + assert.Equal(t, value, c.GetInt32(key)) +} + func TestContextGetInt64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("int64", int64(42424242424242)) @@ -264,12 +288,44 @@ func TestContextGetUint(t *testing.T) { assert.Equal(t, uint(1), c.GetUint("uint")) } +func TestContextGetUint8(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint8" + value := uint8(0xFF) + c.Set(key, value) + assert.Equal(t, value, c.GetUint8(key)) +} + +func TestContextGetUint16(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint16" + value := uint16(0xFFFF) + c.Set(key, value) + assert.Equal(t, value, c.GetUint16(key)) +} + +func TestContextGetUint32(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint32" + value := uint32(0xFFFFFFFF) + c.Set(key, value) + assert.Equal(t, value, c.GetUint32(key)) +} + func TestContextGetUint64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("uint64", uint64(18446744073709551615)) assert.Equal(t, uint64(18446744073709551615), c.GetUint64("uint64")) } +func TestContextGetFloat32(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "float32" + value := float32(3.14) + c.Set(key, value) + assert.Equal(t, value, c.GetFloat32(key)) +} + func TestContextGetFloat64(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("float64", 4.2) @@ -289,6 +345,102 @@ func TestContextGetDuration(t *testing.T) { assert.Equal(t, time.Second, c.GetDuration("duration")) } +func TestContextGetIntSlice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int-slice" + value := []int{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetIntSlice(key)) +} + +func TestContextGetInt8Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int8-slice" + value := []int8{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetInt8Slice(key)) +} + +func TestContextGetInt16Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int16-slice" + value := []int16{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetInt16Slice(key)) +} + +func TestContextGetInt32Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int32-slice" + value := []int32{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetInt32Slice(key)) +} + +func TestContextGetInt64Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "int64-slice" + value := []int64{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetInt64Slice(key)) +} + +func TestContextGetUintSlice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint-slice" + value := []uint{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUintSlice(key)) +} + +func TestContextGetUint8Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint8-slice" + value := []uint8{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUint8Slice(key)) +} + +func TestContextGetUint16Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint16-slice" + value := []uint16{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUint16Slice(key)) +} + +func TestContextGetUint32Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint32-slice" + value := []uint32{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUint32Slice(key)) +} + +func TestContextGetUint64Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "uint64-slice" + value := []uint64{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetUint64Slice(key)) +} + +func TestContextGetFloat32Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "float32-slice" + value := []float32{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetFloat32Slice(key)) +} + +func TestContextGetFloat64Slice(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + key := "float64-slice" + value := []float64{1, 2} + c.Set(key, value) + assert.Equal(t, value, c.GetFloat64Slice(key)) +} + func TestContextGetStringSlice(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Set("slice", []string{"foo"}) From f05f966a0824b1d302ee556183e2579c91954266 Mon Sep 17 00:00:00 2001 From: takanuva15 <6986426+takanuva15@users.noreply.github.com> Date: Sat, 21 Sep 2024 11:24:18 -0400 Subject: [PATCH 887/912] feat(form): Support default values for collections in form binding (#4048) --- binding/form_mapping.go | 20 +++++++++++ binding/form_mapping_test.go | 66 ++++++++++++++++++++++++++++++++++++ docs/doc.md | 48 ++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index a84536f..f5f6f3a 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -159,6 +159,14 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter if k, v := head(opt, "="); k == "default" { setOpt.isDefaultExists = true setOpt.defaultValue = v + + // convert semicolon-separated default values to csv-separated values for processing in setByForm + if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array { + cfTag := field.Tag.Get("collection_format") + if cfTag == "" || cfTag == "multi" || cfTag == "csv" { + setOpt.defaultValue = strings.ReplaceAll(v, ";", ",") + } + } } } @@ -224,6 +232,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ case reflect.Slice: if !ok { vs = []string{opt.defaultValue} + + // pre-process the default value for multi if present + cfTag := field.Tag.Get("collection_format") + if cfTag == "" || cfTag == "multi" { + vs = strings.Split(opt.defaultValue, ",") + } } if ok, err = trySetCustom(vs[0], value); ok { @@ -238,6 +252,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][ case reflect.Array: if !ok { vs = []string{opt.defaultValue} + + // pre-process the default value for multi if present + cfTag := field.Tag.Get("collection_format") + if cfTag == "" || cfTag == "multi" { + vs = strings.Split(opt.defaultValue, ",") + } } if ok, err = trySetCustom(vs[0], value); ok { diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 8cf7465..810315b 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -323,6 +323,72 @@ func TestMappingCollectionFormat(t *testing.T) { assert.Equal(t, [2]int{1, 2}, s.ArrayPipes) } +func TestMappingCollectionFormatInvalid(t *testing.T) { + var s struct { + SliceCsv []int `form:"slice_csv" collection_format:"xxx"` + } + err := mappingByPtr(&s, formSource{ + "slice_csv": {"1,2"}, + }, "form") + require.Error(t, err) + + var s2 struct { + ArrayCsv [2]int `form:"array_csv" collection_format:"xxx"` + } + err = mappingByPtr(&s2, formSource{ + "array_csv": {"1,2"}, + }, "form") + require.Error(t, err) +} + +func TestMappingMultipleDefaultWithCollectionFormat(t *testing.T) { + var s struct { + SliceMulti []int `form:",default=1;2;3" collection_format:"multi"` + SliceCsv []int `form:",default=1;2;3" collection_format:"csv"` + SliceSsv []int `form:",default=1 2 3" collection_format:"ssv"` + SliceTsv []int `form:",default=1\t2\t3" collection_format:"tsv"` + SlicePipes []int `form:",default=1|2|3" collection_format:"pipes"` + ArrayMulti [2]int `form:",default=1;2" collection_format:"multi"` + ArrayCsv [2]int `form:",default=1;2" collection_format:"csv"` + ArraySsv [2]int `form:",default=1 2" collection_format:"ssv"` + ArrayTsv [2]int `form:",default=1\t2" collection_format:"tsv"` + ArrayPipes [2]int `form:",default=1|2" collection_format:"pipes"` + SliceStringMulti []string `form:",default=1;2;3" collection_format:"multi"` + SliceStringCsv []string `form:",default=1;2;3" collection_format:"csv"` + SliceStringSsv []string `form:",default=1 2 3" collection_format:"ssv"` + SliceStringTsv []string `form:",default=1\t2\t3" collection_format:"tsv"` + SliceStringPipes []string `form:",default=1|2|3" collection_format:"pipes"` + ArrayStringMulti [2]string `form:",default=1;2" collection_format:"multi"` + ArrayStringCsv [2]string `form:",default=1;2" collection_format:"csv"` + ArrayStringSsv [2]string `form:",default=1 2" collection_format:"ssv"` + ArrayStringTsv [2]string `form:",default=1\t2" collection_format:"tsv"` + ArrayStringPipes [2]string `form:",default=1|2" collection_format:"pipes"` + } + err := mappingByPtr(&s, formSource{}, "form") + require.NoError(t, err) + + assert.Equal(t, []int{1, 2, 3}, s.SliceMulti) + assert.Equal(t, []int{1, 2, 3}, s.SliceCsv) + assert.Equal(t, []int{1, 2, 3}, s.SliceSsv) + assert.Equal(t, []int{1, 2, 3}, s.SliceTsv) + assert.Equal(t, []int{1, 2, 3}, s.SlicePipes) + assert.Equal(t, [2]int{1, 2}, s.ArrayMulti) + assert.Equal(t, [2]int{1, 2}, s.ArrayCsv) + assert.Equal(t, [2]int{1, 2}, s.ArraySsv) + assert.Equal(t, [2]int{1, 2}, s.ArrayTsv) + assert.Equal(t, [2]int{1, 2}, s.ArrayPipes) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringMulti) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringCsv) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringSsv) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringTsv) + assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringPipes) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringMulti) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringCsv) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringSsv) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringTsv) + assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringPipes) +} + func TestMappingStructField(t *testing.T) { var s struct { J struct { diff --git a/docs/doc.md b/docs/doc.md index 875dd61..8cb53cc 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -26,6 +26,7 @@ - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Bind default value if none provided](#bind-default-value-if-none-provided) - [Collection format for arrays](#collection-format-for-arrays) - [Bind Uri](#bind-uri) - [Bind custom unmarshaler](#bind-custom-unmarshaler) @@ -862,6 +863,53 @@ Test it with: curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" ``` + +### Bind default value if none provided + +If the server should bind a default value to a field when the client does not provide one, specify the default value using the `default` key within the `form` tag: + +``` +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name,default=William"` + Age int `form:"age,default=10"` + Friends []string `form:"friends,default=Will;Bill"` + Addresses [2]string `form:"addresses,default=foo bar" collection_format:"ssv"` + LapTimes []int `form:"lap_times,default=1;2;3" collection_format:"csv"` +} + +func main() { + g := gin.Default() + g.POST("/person", func(c *gin.Context) { + var req Person + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, err) + return + } + c.JSON(http.StatusOK, req) + }) + _ = g.Run("localhost:8080") +} +``` + +``` +curl -X POST http://localhost:8080/person +{"Name":"William","Age":10,"Friends":["Will","Bill"],"Colors":["red","blue"],"LapTimes":[1,2,3]} +``` + +NOTE: For default [collection values](#collection-format-for-arrays), the following rules apply: +- Since commas are used to delimit tag options, they are not supported within a default value and will result in undefined behavior +- For the collection formats "multi" and "csv", a semicolon should be used in place of a comma to delimited default values +- Since semicolons are used to delimit default values for "multi" and "csv", they are not supported within a default value for "multi" and "csv" + + #### Collection format for arrays | Format | Description | Example | From ad740d508f3e98b53ecafda35b66e6a32f6758ac Mon Sep 17 00:00:00 2001 From: wangjingcun Date: Fri, 25 Oct 2024 09:07:03 +0800 Subject: [PATCH 888/912] docs(context): fix some function names in comment (#4079) --- context_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/context_test.go b/context_test.go index 211fbb1..dda5997 100644 --- a/context_test.go +++ b/context_test.go @@ -1153,7 +1153,7 @@ func TestContextRenderNoContentHTMLString(t *testing.T) { assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } -// TestContextData tests that the response can be written from `bytestring` +// TestContextRenderData tests that the response can be written from `bytestring` // with specified MIME type func TestContextRenderData(t *testing.T) { w := httptest.NewRecorder() @@ -1550,7 +1550,7 @@ func TestContextIsAborted(t *testing.T) { assert.True(t, c.IsAborted()) } -// TestContextData tests that the response can be written from `bytestring` +// TestContextAbortWithStatus tests that the response can be written from `bytestring` // with specified MIME type func TestContextAbortWithStatus(t *testing.T) { w := httptest.NewRecorder() From b080116a7f5c71f023e1059ebb9e99a799938909 Mon Sep 17 00:00:00 2001 From: Enzo Lanzellotti <102574758+YlanzinhoY@users.noreply.github.com> Date: Thu, 24 Oct 2024 22:08:11 -0300 Subject: [PATCH 889/912] docs(readme): add Portuguese documentation. (#4078) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index faeb495..0464107 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in - [한국어](https://gin-gonic.com/ko-kr/docs/) - [Turkish](https://gin-gonic.com/tr/docs/) - [Persian](https://gin-gonic.com/fa/docs/) +- [Português](https://gin-gonic.com/pt/docs/) ### Articles From 299c6f30e3df4c5a257c517a91f421ff3ea63a8e Mon Sep 17 00:00:00 2001 From: tsukasa-ino Date: Fri, 25 Oct 2024 10:16:40 +0900 Subject: [PATCH 890/912] docs: trimmed some white spaces (#4070) --- docs/doc.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/doc.md b/docs/doc.md index 8cb53cc..a463e82 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -172,7 +172,7 @@ func main() { router := gin.Default() // Query string parameters are parsed using the existing underlying request object. - // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe + // The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") @@ -516,19 +516,19 @@ Sample Output ```go func main() { router := gin.New() - + // skip logging for desired paths by setting SkipPaths in LoggerConfig loggerConfig := gin.LoggerConfig{SkipPaths: []string{"/metrics"}} - + // skip logging based on your logic by setting Skip func in LoggerConfig loggerConfig.Skip = func(c *gin.Context) bool { // as an example skip non server side errors return c.Writer.Status() < http.StatusInternalServerError } - + router.Use(gin.LoggerWithConfig(loggerConfig)) router.Use(gin.Recovery()) - + // skipped router.GET("/metrics", func(c *gin.Context) { c.Status(http.StatusNotImplemented) @@ -543,7 +543,7 @@ func main() { router.GET("/data", func(c *gin.Context) { c.Status(http.StatusNotImplemented) }) - + router.Run(":8080") } @@ -615,7 +615,7 @@ You can also specify that specific fields are required. If a field is decorated ```go // Binding from JSON type Login struct { - User string `form:"user" json:"user" xml:"user" binding:"required"` + User string `form:"user" json:"user" xml:"user" binding:"required"` Password string `form:"password" json:"password" xml:"password" binding:"required"` } @@ -1252,7 +1252,7 @@ func main() { #### JSONP -Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. +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() { @@ -1301,7 +1301,7 @@ func main() { #### PureJSON -Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. +Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead. This feature is unavailable in Go 1.6 and lower. ```go @@ -1336,7 +1336,7 @@ func main() { router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFile("/favicon.ico", "./resources/favicon.ico") router.StaticFileFS("/more_favicon.ico", "more_favicon.ico", http.Dir("my_file_system")) - + // Listen and serve on 0.0.0.0:8080 router.Run(":8080") } @@ -2320,7 +2320,7 @@ or network CIDRs from where clients which their request headers related to clien IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs. -**Attention:** Gin trust all proxies by default if you don't specify a trusted +**Attention:** Gin trust all proxies by default if you don't specify a trusted proxy using the function above, **this is NOT safe**. At the same time, if you don't use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`, then `Context.ClientIP()` will return the remote address directly to avoid some @@ -2349,7 +2349,7 @@ func main() { ``` **Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` -to skip TrustedProxies check, it has a higher priority than TrustedProxies. +to skip TrustedProxies check, it has a higher priority than TrustedProxies. Look at the example below: ```go From 647311aba203dd7262b24f973503e7689e00389d Mon Sep 17 00:00:00 2001 From: Xinyu Kuo Date: Fri, 25 Oct 2024 09:33:31 +0800 Subject: [PATCH 891/912] refactor(context): refactor context handling and improve test robustness (#4066) Use assert.InDelta for float comparison with tolerance in TestContextGetFloat32 Remove unnecessary blank line in TestContextInitQueryCache Replace anonymous struct with named contextKey type in TestContextWithFallbackValueFromRequestContext Update context key handling in TestContextWithFallbackValueFromRequestContext to use contextKey type --- context.go | 2 +- context_test.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 1f35651..77232f9 100644 --- a/context.go +++ b/context.go @@ -951,7 +951,7 @@ func (c *Context) ShouldBindBodyWithTOML(obj any) error { return c.ShouldBindBodyWith(obj, binding.TOML) } -// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). +// ShouldBindBodyWithPlain is a shortcut for c.ShouldBindBodyWith(obj, binding.Plain). func (c *Context) ShouldBindBodyWithPlain(obj any) error { return c.ShouldBindBodyWith(obj, binding.Plain) } diff --git a/context_test.go b/context_test.go index dda5997..62a1e14 100644 --- a/context_test.go +++ b/context_test.go @@ -323,7 +323,7 @@ func TestContextGetFloat32(t *testing.T) { key := "float32" value := float32(3.14) c.Set(key, value) - assert.Equal(t, value, c.GetFloat32(key)) + assert.InDelta(t, value, c.GetFloat32(key), 0.01) } func TestContextGetFloat64(t *testing.T) { @@ -2857,7 +2857,8 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { { name: "c with struct context key", getContextAndKey: func() (*Context, any) { - var key struct{} + type KeyStruct struct{} // https://staticcheck.dev/docs/checks/#SA1029 + var key KeyStruct c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true From 9d11234efec1e5517b2887a6e7dfbc9c017bc52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Karpi=C5=84ski?= Date: Sat, 26 Oct 2024 02:26:25 +0200 Subject: [PATCH 892/912] docs(gin): Replace broken link to documentation with valid (#4064) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 48cc15c..e17596a 100644 --- a/gin.go +++ b/gin.go @@ -598,7 +598,7 @@ func (engine *Engine) RunQUIC(addr, certFile, keyFile string) (err error) { if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + - "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") + "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.") } err = http3.ListenAndServeQUIC(addr, certFile, keyFile, engine.Handler()) From ea53388e6ee4a6a0a1647b390c56eeed780e7e56 Mon Sep 17 00:00:00 2001 From: Xinyu Kuo Date: Sat, 26 Oct 2024 08:28:59 +0800 Subject: [PATCH 893/912] fix(tree): Keep panic infos consistent when wildcard type build faild (#4077) --- .github/workflows/gin.yml | 2 +- tree.go | 2 +- tree_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 947abf9..74983c5 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -26,7 +26,7 @@ jobs: - name: Setup golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.58.1 + version: v1.61.0 args: --verbose test: needs: lint diff --git a/tree.go b/tree.go index b0a5f98..0d3e5a8 100644 --- a/tree.go +++ b/tree.go @@ -369,7 +369,7 @@ func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) // currently fixed width 1 for '/' i-- - if path[i] != '/' { + if i < 0 || path[i] != '/' { panic("no / before catch-all in path '" + fullPath + "'") } diff --git a/tree_test.go b/tree_test.go index 3aa3a59..74eb610 100644 --- a/tree_test.go +++ b/tree_test.go @@ -993,3 +993,28 @@ func TestTreeInvalidEscape(t *testing.T) { } } } + +func TestWildcardInvalidSlash(t *testing.T) { + const panicMsgPrefix = "no / before catch-all in path" + + routes := map[string]bool{ + "/foo/bar": true, + "/foo/x*zy": false, + "/foo/b*r": false, + } + + for route, valid := range routes { + tree := &node{} + recv := catchPanic(func() { + tree.addRoute(route, nil) + }) + + if recv == nil != valid { + t.Fatalf("%s should be %t but got %v", route, valid, recv) + } + + if rs, ok := recv.(string); recv != nil && (!ok || !strings.HasPrefix(rs, panicMsgPrefix)) { + t.Fatalf(`"Expected panic "%s" for route '%s', got "%v"`, panicMsgPrefix, route, recv) + } + } +} From c8a3adc65703d8958265c07689662e54f037038c Mon Sep 17 00:00:00 2001 From: Konovalov Maxim <43151027+KaymeKaydex@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:24:53 +0300 Subject: [PATCH 894/912] refactor(context): simplify "GetType()" functions (#4080) This PR introduces a generic function, getTyped[T any], to simplify value retrieval in the Context struct. It replaces repetitive type assertions in the GetString GetBool etc. methods. Co-authored-by: Maksim Konovalov --- context.go | 177 +++++++++++++++-------------------------------------- 1 file changed, 50 insertions(+), 127 deletions(-) diff --git a/context.go b/context.go index 77232f9..1e8c8e2 100644 --- a/context.go +++ b/context.go @@ -291,248 +291,171 @@ func (c *Context) MustGet(key string) any { panic("Key \"" + key + "\" does not exist") } -// GetString returns the value associated with the key as a string. -func (c *Context) GetString(key string) (s string) { +func getTyped[T any](c *Context, key string) (res T) { if val, ok := c.Get(key); ok && val != nil { - s, _ = val.(string) + res, _ = val.(T) } return } +// GetString returns the value associated with the key as a string. +func (c *Context) GetString(key string) (s string) { + return getTyped[string](c, key) +} + // GetBool returns the value associated with the key as a boolean. func (c *Context) GetBool(key string) (b bool) { - if val, ok := c.Get(key); ok && val != nil { - b, _ = val.(bool) - } - return + return getTyped[bool](c, key) } // GetInt returns the value associated with the key as an integer. func (c *Context) GetInt(key string) (i int) { - if val, ok := c.Get(key); ok && val != nil { - i, _ = val.(int) - } - return + return getTyped[int](c, key) } // GetInt8 returns the value associated with the key as an integer 8. func (c *Context) GetInt8(key string) (i8 int8) { - if val, ok := c.Get(key); ok && val != nil { - i8, _ = val.(int8) - } - return + return getTyped[int8](c, key) } // GetInt16 returns the value associated with the key as an integer 16. func (c *Context) GetInt16(key string) (i16 int16) { - if val, ok := c.Get(key); ok && val != nil { - i16, _ = val.(int16) - } - return + return getTyped[int16](c, key) } // GetInt32 returns the value associated with the key as an integer 32. func (c *Context) GetInt32(key string) (i32 int32) { - if val, ok := c.Get(key); ok && val != nil { - i32, _ = val.(int32) - } - return + return getTyped[int32](c, key) } // GetInt64 returns the value associated with the key as an integer 64. func (c *Context) GetInt64(key string) (i64 int64) { - if val, ok := c.Get(key); ok && val != nil { - i64, _ = val.(int64) - } - return + return getTyped[int64](c, key) } // GetUint returns the value associated with the key as an unsigned integer. func (c *Context) GetUint(key string) (ui uint) { - if val, ok := c.Get(key); ok && val != nil { - ui, _ = val.(uint) - } - return + return getTyped[uint](c, key) } // GetUint8 returns the value associated with the key as an unsigned integer 8. func (c *Context) GetUint8(key string) (ui8 uint8) { - if val, ok := c.Get(key); ok && val != nil { - ui8, _ = val.(uint8) - } - return + return getTyped[uint8](c, key) } // GetUint16 returns the value associated with the key as an unsigned integer 16. func (c *Context) GetUint16(key string) (ui16 uint16) { - if val, ok := c.Get(key); ok && val != nil { - ui16, _ = val.(uint16) - } - return + return getTyped[uint16](c, key) } // GetUint32 returns the value associated with the key as an unsigned integer 32. func (c *Context) GetUint32(key string) (ui32 uint32) { - if val, ok := c.Get(key); ok && val != nil { - ui32, _ = val.(uint32) - } - return + return getTyped[uint32](c, key) } // GetUint64 returns the value associated with the key as an unsigned integer 64. func (c *Context) GetUint64(key string) (ui64 uint64) { - if val, ok := c.Get(key); ok && val != nil { - ui64, _ = val.(uint64) - } - return + return getTyped[uint64](c, key) } // GetFloat32 returns the value associated with the key as a float32. func (c *Context) GetFloat32(key string) (f32 float32) { - if val, ok := c.Get(key); ok && val != nil { - f32, _ = val.(float32) - } - return + return getTyped[float32](c, key) } // GetFloat64 returns the value associated with the key as a float64. func (c *Context) GetFloat64(key string) (f64 float64) { - if val, ok := c.Get(key); ok && val != nil { - f64, _ = val.(float64) - } - return + return getTyped[float64](c, key) } // GetTime returns the value associated with the key as time. func (c *Context) GetTime(key string) (t time.Time) { - if val, ok := c.Get(key); ok && val != nil { - t, _ = val.(time.Time) - } - return + return getTyped[time.Time](c, key) } // GetDuration returns the value associated with the key as a duration. func (c *Context) GetDuration(key string) (d time.Duration) { - if val, ok := c.Get(key); ok && val != nil { - d, _ = val.(time.Duration) - } - return + return getTyped[time.Duration](c, key) } +// GetIntSlice returns the value associated with the key as a slice of integers. func (c *Context) GetIntSlice(key string) (is []int) { - if val, ok := c.Get(key); ok && val != nil { - is, _ = val.([]int) - } - return + return getTyped[[]int](c, key) } +// GetInt8Slice returns the value associated with the key as a slice of int8 integers. func (c *Context) GetInt8Slice(key string) (i8s []int8) { - if val, ok := c.Get(key); ok && val != nil { - i8s, _ = val.([]int8) - } - return + return getTyped[[]int8](c, key) } +// GetInt16Slice returns the value associated with the key as a slice of int16 integers. func (c *Context) GetInt16Slice(key string) (i16s []int16) { - if val, ok := c.Get(key); ok && val != nil { - i16s, _ = val.([]int16) - } - return + return getTyped[[]int16](c, key) } +// GetInt32Slice returns the value associated with the key as a slice of int32 integers. func (c *Context) GetInt32Slice(key string) (i32s []int32) { - if val, ok := c.Get(key); ok && val != nil { - i32s, _ = val.([]int32) - } - return + return getTyped[[]int32](c, key) } +// GetInt64Slice returns the value associated with the key as a slice of int64 integers. func (c *Context) GetInt64Slice(key string) (i64s []int64) { - if val, ok := c.Get(key); ok && val != nil { - i64s, _ = val.([]int64) - } - return + return getTyped[[]int64](c, key) } +// GetUintSlice returns the value associated with the key as a slice of unsigned integers. func (c *Context) GetUintSlice(key string) (uis []uint) { - if val, ok := c.Get(key); ok && val != nil { - uis, _ = val.([]uint) - } - return + return getTyped[[]uint](c, key) } +// GetUint8Slice returns the value associated with the key as a slice of uint8 integers. func (c *Context) GetUint8Slice(key string) (ui8s []uint8) { - if val, ok := c.Get(key); ok && val != nil { - ui8s, _ = val.([]uint8) - } - return + return getTyped[[]uint8](c, key) } +// GetUint16Slice returns the value associated with the key as a slice of uint16 integers. func (c *Context) GetUint16Slice(key string) (ui16s []uint16) { - if val, ok := c.Get(key); ok && val != nil { - ui16s, _ = val.([]uint16) - } - return + return getTyped[[]uint16](c, key) } +// GetUint32Slice returns the value associated with the key as a slice of uint32 integers. func (c *Context) GetUint32Slice(key string) (ui32s []uint32) { - if val, ok := c.Get(key); ok && val != nil { - ui32s, _ = val.([]uint32) - } - return + return getTyped[[]uint32](c, key) } +// GetUint64Slice returns the value associated with the key as a slice of uint64 integers. func (c *Context) GetUint64Slice(key string) (ui64s []uint64) { - if val, ok := c.Get(key); ok && val != nil { - ui64s, _ = val.([]uint64) - } - return + return getTyped[[]uint64](c, key) } +// GetFloat32Slice returns the value associated with the key as a slice of float32 numbers. func (c *Context) GetFloat32Slice(key string) (f32s []float32) { - if val, ok := c.Get(key); ok && val != nil { - f32s, _ = val.([]float32) - } - return + return getTyped[[]float32](c, key) } +// GetFloat64Slice returns the value associated with the key as a slice of float64 numbers. func (c *Context) GetFloat64Slice(key string) (f64s []float64) { - if val, ok := c.Get(key); ok && val != nil { - f64s, _ = val.([]float64) - } - return + return getTyped[[]float64](c, key) } // GetStringSlice returns the value associated with the key as a slice of strings. func (c *Context) GetStringSlice(key string) (ss []string) { - if val, ok := c.Get(key); ok && val != nil { - ss, _ = val.([]string) - } - return + return getTyped[[]string](c, key) } // GetStringMap returns the value associated with the key as a map of interfaces. func (c *Context) GetStringMap(key string) (sm map[string]any) { - if val, ok := c.Get(key); ok && val != nil { - sm, _ = val.(map[string]any) - } - return + return getTyped[map[string]any](c, key) } // GetStringMapString returns the value associated with the key as a map of strings. func (c *Context) GetStringMapString(key string) (sms map[string]string) { - if val, ok := c.Get(key); ok && val != nil { - sms, _ = val.(map[string]string) - } - return + return getTyped[map[string]string](c, key) } // GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) { - if val, ok := c.Get(key); ok && val != nil { - smss, _ = val.(map[string][]string) - } - return + return getTyped[map[string][]string](c, key) } /************************************/ From f875d8728306c2c2c6f504900ab08cd1d8c47f12 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 15 Nov 2024 23:49:08 +0800 Subject: [PATCH 895/912] chore(context): test context initialization and handler logic (#4087) * enhance code imported by #3413 if it needs to check if the handler is nil, tie c.index shall always ++ * test: refactor test context initialization and handler logic - Remove an empty line in `TestContextInitQueryCache` - Add `TestContextNext` function with tests for `Next` method behavior with no handlers, one handler, and multiple handlers Signed-off-by: Bo-Yi Wu --------- Signed-off-by: Bo-Yi Wu Co-authored-by: zjj --- context.go | 5 ++--- context_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/context.go b/context.go index 1e8c8e2..cab1452 100644 --- a/context.go +++ b/context.go @@ -186,10 +186,9 @@ func (c *Context) FullPath() string { func (c *Context) Next() { c.index++ for c.index < int8(len(c.handlers)) { - if c.handlers[c.index] == nil { - continue + if c.handlers[c.index] != nil { + c.handlers[c.index](c) } - c.handlers[c.index](c) c.index++ } } diff --git a/context_test.go b/context_test.go index 62a1e14..6921dea 100644 --- a/context_test.go +++ b/context_test.go @@ -610,7 +610,6 @@ func TestContextInitQueryCache(t *testing.T) { assert.Equal(t, test.expectedQueryCache, test.testContext.queryCache) }) } - } func TestContextDefaultQueryOnEmptyRequest(t *testing.T) { @@ -3038,3 +3037,47 @@ func TestInterceptedHeader(t *testing.T) { assert.Equal(t, "", w.Result().Header.Get("X-Test")) assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) } + +func TestContextNext(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + + // Test with no handlers + c.Next() + assert.Equal(t, int8(0), c.index) + + // Test with one handler + c.index = -1 + c.handlers = HandlersChain{func(c *Context) { + c.Set("key", "value") + }} + c.Next() + assert.Equal(t, int8(1), c.index) + value, exists := c.Get("key") + assert.True(t, exists) + assert.Equal(t, "value", value) + + // Test with multiple handlers + c.handlers = HandlersChain{ + func(c *Context) { + c.Set("key1", "value1") + c.Next() + c.Set("key2", "value2") + }, + nil, + func(c *Context) { + c.Set("key3", "value3") + }, + } + c.index = -1 + c.Next() + assert.Equal(t, int8(4), c.index) + value, exists = c.Get("key1") + assert.True(t, exists) + assert.Equal(t, "value1", value) + value, exists = c.Get("key2") + assert.True(t, exists) + assert.Equal(t, "value2", value) + value, exists = c.Get("key3") + assert.True(t, exists) + assert.Equal(t, "value3", value) +} From 02c1144f312eaf18767475a578bc421ddbcc4b82 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 15 Nov 2024 16:51:12 +0100 Subject: [PATCH 896/912] ci(lint): enable perfsprint linter (#4090) Signed-off-by: Matthieu MOREL --- .golangci.yml | 8 ++++++++ binding/form_mapping_test.go | 8 ++++---- context_test.go | 5 +++-- gin_test.go | 20 ++++++++++---------- githubapi_test.go | 5 +++-- recovery_test.go | 3 +-- routes_test.go | 4 ++-- 7 files changed, 31 insertions(+), 22 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 8d58c98..c3ae727 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,6 +16,7 @@ linters: - nakedret - nilerr - nolintlint + - perfsprint - revive - testifylint - wastedassign @@ -34,6 +35,13 @@ linters-settings: - G112 - G201 - G203 + perfsprint: + err-error: true + errorf: true + fiximports: true + int-conversion: true + sprintf1: true + strconcat: true testifylint: enable-all: true diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go index 810315b..1277fd5 100644 --- a/binding/form_mapping_test.go +++ b/binding/form_mapping_test.go @@ -6,7 +6,7 @@ package binding import ( "encoding/hex" - "fmt" + "errors" "mime/multipart" "reflect" "strconv" @@ -494,7 +494,7 @@ type customUnmarshalParamType struct { func (f *customUnmarshalParamType) UnmarshalParam(param string) error { parts := strings.Split(param, ":") if len(parts) != 3 { - return fmt.Errorf("invalid format") + return errors.New("invalid format") } f.Protocol = parts[0] f.Path = parts[1] @@ -556,7 +556,7 @@ func (p *customPath) UnmarshalParam(param string) error { elems := strings.Split(param, "/") n := len(elems) if n < 2 { - return fmt.Errorf("invalid format") + return errors.New("invalid format") } *p = elems @@ -600,7 +600,7 @@ func (o *objectID) UnmarshalParam(param string) error { func convertTo(s string) (objectID, error) { var nilObjectID objectID if len(s) != 24 { - return nilObjectID, fmt.Errorf("invalid format") + return nilObjectID, errors.New("invalid format") } var oid [12]byte diff --git a/context_test.go b/context_test.go index 6921dea..7c41484 100644 --- a/context_test.go +++ b/context_test.go @@ -18,6 +18,7 @@ import ( "net/url" "os" "reflect" + "strconv" "strings" "sync" "testing" @@ -2633,7 +2634,7 @@ func TestContextRenderDataFromReader(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, body, w.Body.String()) assert.Equal(t, contentType, w.Header().Get("Content-Type")) - assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length")) + assert.Equal(t, strconv.FormatInt(contentLength, 10), w.Header().Get("Content-Length")) assert.Equal(t, extraHeaders["Content-Disposition"], w.Header().Get("Content-Disposition")) } @@ -2651,7 +2652,7 @@ func TestContextRenderDataFromReaderNoHeaders(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, body, w.Body.String()) assert.Equal(t, contentType, w.Header().Get("Content-Type")) - assert.Equal(t, fmt.Sprintf("%d", contentLength), w.Header().Get("Content-Length")) + assert.Equal(t, strconv.FormatInt(contentLength, 10), w.Header().Get("Content-Length")) } type TestResponseRecorder struct { diff --git a/gin_test.go b/gin_test.go index 719f63e..5d0c47d 100644 --- a/gin_test.go +++ b/gin_test.go @@ -73,7 +73,7 @@ func TestLoadHTMLGlobDebugMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -131,7 +131,7 @@ func TestLoadHTMLGlobTestMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -151,7 +151,7 @@ func TestLoadHTMLGlobReleaseMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -178,7 +178,7 @@ func TestLoadHTMLGlobUsingTLS(t *testing.T) { }, } client := &http.Client{Transport: tr} - res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := client.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -198,7 +198,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) + res, err := http.Get(ts.URL + "/raw") if err != nil { t.Error(err) } @@ -229,7 +229,7 @@ func TestLoadHTMLFilesTestMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -249,7 +249,7 @@ func TestLoadHTMLFilesDebugMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -269,7 +269,7 @@ func TestLoadHTMLFilesReleaseMode(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := http.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -296,7 +296,7 @@ func TestLoadHTMLFilesUsingTLS(t *testing.T) { }, } client := &http.Client{Transport: tr} - res, err := client.Get(fmt.Sprintf("%s/test", ts.URL)) + res, err := client.Get(ts.URL + "/test") if err != nil { t.Error(err) } @@ -316,7 +316,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { ) defer ts.Close() - res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL)) + res, err := http.Get(ts.URL + "/raw") if err != nil { t.Error(err) } diff --git a/githubapi_test.go b/githubapi_test.go index 6d34878..0c86af2 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httptest" "os" + "strconv" "strings" "testing" @@ -411,7 +412,7 @@ func exampleFromPath(path string) (string, Params) { } if start >= 0 { if c == '/' { - value := fmt.Sprint(rand.Intn(100000)) + value := strconv.Itoa(rand.Intn(100000)) params = append(params, Param{ Key: path[start:i], Value: value, @@ -425,7 +426,7 @@ func exampleFromPath(path string) (string, Params) { } } if start >= 0 { - value := fmt.Sprint(rand.Intn(100000)) + value := strconv.Itoa(rand.Intn(100000)) params = append(params, Param{ Key: path[start:], Value: value, diff --git a/recovery_test.go b/recovery_test.go index fa8ab89..ee063cd 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -5,7 +5,6 @@ package gin import ( - "fmt" "net" "net/http" "os" @@ -33,7 +32,7 @@ func TestPanicClean(t *testing.T) { }, header{ Key: "Authorization", - Value: fmt.Sprintf("Bearer %s", password), + Value: "Bearer " + password, }, header{ Key: "Content-Type", diff --git a/routes_test.go b/routes_test.go index d6233b0..49f355a 100644 --- a/routes_test.go +++ b/routes_test.go @@ -560,7 +560,7 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) { w := PerformRequest(router, "GET", tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { - assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) + assert.Equal(t, tr.location, w.Header().Get("Location")) } } } @@ -590,7 +590,7 @@ func TestRouterNotFound(t *testing.T) { w := PerformRequest(router, http.MethodGet, tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { - assert.Equal(t, tr.location, fmt.Sprint(w.Header().Get("Location"))) + assert.Equal(t, tr.location, w.Header().Get("Location")) } } From e8d34d053f7008858886b8e4f76b3e8564105870 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 15 Nov 2024 16:52:16 +0100 Subject: [PATCH 897/912] ci(lint): enable usestdlibvars linter (#4091) Signed-off-by: Matthieu MOREL --- .golangci.yml | 1 + auth_test.go | 10 +- benchmarks_test.go | 24 +-- binding/binding_msgpack_test.go | 9 +- binding/binding_test.go | 232 ++++++++++++------------- binding/multipart_form_mapping_test.go | 2 +- context_test.go | 154 ++++++++-------- debug_test.go | 5 +- deprecated_test.go | 2 +- gin_test.go | 48 ++--- logger_test.go | 84 ++++----- middleware_test.go | 16 +- recovery_test.go | 22 +-- render/render_test.go | 2 +- routes_test.go | 8 +- utils_test.go | 12 +- 16 files changed, 317 insertions(+), 314 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c3ae727..ccb2668 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,7 @@ linters: - perfsprint - revive - testifylint + - usestdlibvars - wastedassign linters-settings: diff --git a/auth_test.go b/auth_test.go index f717592..9166e3b 100644 --- a/auth_test.go +++ b/auth_test.go @@ -90,7 +90,7 @@ func TestBasicAuthSucceed(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/login", nil) + req, _ := http.NewRequest(http.MethodGet, "/login", nil) req.Header.Set("Authorization", authorizationHeader("admin", "password")) router.ServeHTTP(w, req) @@ -109,7 +109,7 @@ func TestBasicAuth401(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/login", nil) + req, _ := http.NewRequest(http.MethodGet, "/login", nil) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) router.ServeHTTP(w, req) @@ -129,7 +129,7 @@ func TestBasicAuth401WithCustomRealm(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/login", nil) + req, _ := http.NewRequest(http.MethodGet, "/login", nil) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) router.ServeHTTP(w, req) @@ -147,7 +147,7 @@ func TestBasicAuthForProxySucceed(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("Proxy-Authorization", authorizationHeader("admin", "password")) router.ServeHTTP(w, req) @@ -166,7 +166,7 @@ func TestBasicAuthForProxy407(t *testing.T) { }) w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/test", nil) + req, _ := http.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("admin:password"))) router.ServeHTTP(w, req) diff --git a/benchmarks_test.go b/benchmarks_test.go index 5b7929b..3a8d53f 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -14,21 +14,21 @@ import ( func BenchmarkOneRoute(B *testing.B) { router := New() router.GET("/ping", func(c *Context) {}) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func BenchmarkRecoveryMiddleware(B *testing.B) { router := New() router.Use(Recovery()) router.GET("/", func(c *Context) {}) - runRequest(B, router, "GET", "/") + runRequest(B, router, http.MethodGet, "/") } func BenchmarkLoggerMiddleware(B *testing.B) { router := New() router.Use(LoggerWithWriter(newMockWriter())) router.GET("/", func(c *Context) {}) - runRequest(B, router, "GET", "/") + runRequest(B, router, http.MethodGet, "/") } func BenchmarkManyHandlers(B *testing.B) { @@ -37,7 +37,7 @@ func BenchmarkManyHandlers(B *testing.B) { router.Use(func(c *Context) {}) router.Use(func(c *Context) {}) router.GET("/ping", func(c *Context) {}) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func Benchmark5Params(B *testing.B) { @@ -45,7 +45,7 @@ func Benchmark5Params(B *testing.B) { router := New() router.Use(func(c *Context) {}) router.GET("/param/:param1/:params2/:param3/:param4/:param5", func(c *Context) {}) - runRequest(B, router, "GET", "/param/path/to/parameter/john/12345") + runRequest(B, router, http.MethodGet, "/param/path/to/parameter/john/12345") } func BenchmarkOneRouteJSON(B *testing.B) { @@ -56,7 +56,7 @@ func BenchmarkOneRouteJSON(B *testing.B) { router.GET("/json", func(c *Context) { c.JSON(http.StatusOK, data) }) - runRequest(B, router, "GET", "/json") + runRequest(B, router, http.MethodGet, "/json") } func BenchmarkOneRouteHTML(B *testing.B) { @@ -68,7 +68,7 @@ func BenchmarkOneRouteHTML(B *testing.B) { router.GET("/html", func(c *Context) { c.HTML(http.StatusOK, "index", "hola") }) - runRequest(B, router, "GET", "/html") + runRequest(B, router, http.MethodGet, "/html") } func BenchmarkOneRouteSet(B *testing.B) { @@ -76,7 +76,7 @@ func BenchmarkOneRouteSet(B *testing.B) { router.GET("/ping", func(c *Context) { c.Set("key", "value") }) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func BenchmarkOneRouteString(B *testing.B) { @@ -84,13 +84,13 @@ func BenchmarkOneRouteString(B *testing.B) { router.GET("/text", func(c *Context) { c.String(http.StatusOK, "this is a plain text") }) - runRequest(B, router, "GET", "/text") + runRequest(B, router, http.MethodGet, "/text") } func BenchmarkManyRoutesFist(B *testing.B) { router := New() router.Any("/ping", func(c *Context) {}) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func BenchmarkManyRoutesLast(B *testing.B) { @@ -103,7 +103,7 @@ func Benchmark404(B *testing.B) { router := New() router.Any("/something", func(c *Context) {}) router.NoRoute(func(c *Context) {}) - runRequest(B, router, "GET", "/ping") + runRequest(B, router, http.MethodGet, "/ping") } func Benchmark404Many(B *testing.B) { @@ -118,7 +118,7 @@ func Benchmark404Many(B *testing.B) { router.GET("/user/:id/:mode", func(c *Context) {}) router.NoRoute(func(c *Context) {}) - runRequest(B, router, "GET", "/viewfake") + runRequest(B, router, http.MethodGet, "/viewfake") } type mockWriter struct { diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go index a811639..7a5db34 100644 --- a/binding/binding_msgpack_test.go +++ b/binding/binding_msgpack_test.go @@ -8,6 +8,7 @@ package binding import ( "bytes" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -39,20 +40,20 @@ func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, assert.Equal(t, name, b.Name()) obj := FooStruct{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) req.Header.Add("Content-Type", MIMEMSGPACK) err := b.Bind(req, &obj) require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) req.Header.Add("Content-Type", MIMEMSGPACK) err = MsgPack.Bind(req, &obj) require.Error(t, err) } func TestBindingDefaultMsgPack(t *testing.T) { - assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) - assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) + assert.Equal(t, MsgPack, Default(http.MethodPost, MIMEMSGPACK)) + assert.Equal(t, MsgPack, Default(http.MethodPut, MIMEMSGPACK2)) } diff --git a/binding/binding_test.go b/binding/binding_test.go index 2036b59..901e974 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -145,31 +145,31 @@ type FooStructForMapPtrType struct { } func TestBindingDefault(t *testing.T) { - assert.Equal(t, Form, Default("GET", "")) - assert.Equal(t, Form, Default("GET", MIMEJSON)) + assert.Equal(t, Form, Default(http.MethodGet, "")) + assert.Equal(t, Form, Default(http.MethodGet, MIMEJSON)) - assert.Equal(t, JSON, Default("POST", MIMEJSON)) - assert.Equal(t, JSON, Default("PUT", MIMEJSON)) + assert.Equal(t, JSON, Default(http.MethodPost, MIMEJSON)) + assert.Equal(t, JSON, Default(http.MethodPut, MIMEJSON)) - assert.Equal(t, XML, Default("POST", MIMEXML)) - assert.Equal(t, XML, Default("PUT", MIMEXML2)) + assert.Equal(t, XML, Default(http.MethodPost, MIMEXML)) + assert.Equal(t, XML, Default(http.MethodPut, MIMEXML2)) - assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) - assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) + assert.Equal(t, Form, Default(http.MethodPost, MIMEPOSTForm)) + assert.Equal(t, Form, Default(http.MethodPut, MIMEPOSTForm)) - assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm)) - assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default(http.MethodPost, MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default(http.MethodPut, MIMEMultipartPOSTForm)) - assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) - assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) + assert.Equal(t, ProtoBuf, Default(http.MethodPost, MIMEPROTOBUF)) + assert.Equal(t, ProtoBuf, Default(http.MethodPut, MIMEPROTOBUF)) - assert.Equal(t, YAML, Default("POST", MIMEYAML)) - assert.Equal(t, YAML, Default("PUT", MIMEYAML)) - assert.Equal(t, YAML, Default("POST", MIMEYAML2)) - assert.Equal(t, YAML, Default("PUT", MIMEYAML2)) + assert.Equal(t, YAML, Default(http.MethodPost, MIMEYAML)) + assert.Equal(t, YAML, Default(http.MethodPut, MIMEYAML)) + assert.Equal(t, YAML, Default(http.MethodPost, MIMEYAML2)) + assert.Equal(t, YAML, Default(http.MethodPut, MIMEYAML2)) - assert.Equal(t, TOML, Default("POST", MIMETOML)) - assert.Equal(t, TOML, Default("PUT", MIMETOML)) + assert.Equal(t, TOML, Default(http.MethodPost, MIMETOML)) + assert.Equal(t, TOML, Default(http.MethodPut, MIMETOML)) } func TestBindingJSONNilBody(t *testing.T) { @@ -227,137 +227,137 @@ func TestBindingJSONStringMap(t *testing.T) { } func TestBindingForm(t *testing.T) { - testFormBinding(t, "POST", + testFormBinding(t, http.MethodPost, "/", "/", "foo=bar&bar=foo", "bar2=foo") } func TestBindingForm2(t *testing.T) { - testFormBinding(t, "GET", + testFormBinding(t, http.MethodGet, "/?foo=bar&bar=foo", "/?bar2=foo", "", "") } func TestBindingFormEmbeddedStruct(t *testing.T) { - testFormBindingEmbeddedStruct(t, "POST", + testFormBindingEmbeddedStruct(t, http.MethodPost, "/", "/", "page=1&size=2&appkey=test-appkey", "bar2=foo") } func TestBindingFormEmbeddedStruct2(t *testing.T) { - testFormBindingEmbeddedStruct(t, "GET", + testFormBindingEmbeddedStruct(t, http.MethodGet, "/?page=1&size=2&appkey=test-appkey", "/?bar2=foo", "", "") } func TestBindingFormDefaultValue(t *testing.T) { - testFormBindingDefaultValue(t, "POST", + testFormBindingDefaultValue(t, http.MethodPost, "/", "/", "foo=bar", "bar2=foo") } func TestBindingFormDefaultValue2(t *testing.T) { - testFormBindingDefaultValue(t, "GET", + testFormBindingDefaultValue(t, http.MethodGet, "/?foo=bar", "/?bar2=foo", "", "") } func TestBindingFormForTime(t *testing.T) { - testFormBindingForTime(t, "POST", + testFormBindingForTime(t, http.MethodPost, "/", "/", "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo") - testFormBindingForTimeNotUnixFormat(t, "POST", + testFormBindingForTimeNotUnixFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") - testFormBindingForTimeNotFormat(t, "POST", + testFormBindingForTimeNotFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15", "bar2=foo") - testFormBindingForTimeFailFormat(t, "POST", + testFormBindingForTimeFailFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15", "bar2=foo") - testFormBindingForTimeFailLocation(t, "POST", + testFormBindingForTimeFailLocation(t, http.MethodPost, "/", "/", "time_foo=2017-11-15", "bar2=foo") } func TestBindingFormForTime2(t *testing.T) { - testFormBindingForTime(t, "GET", + testFormBindingForTime(t, http.MethodGet, "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo", "", "") - testFormBindingForTimeNotUnixFormat(t, "POST", + testFormBindingForTimeNotUnixFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") - testFormBindingForTimeNotFormat(t, "GET", + testFormBindingForTimeNotFormat(t, http.MethodGet, "/?time_foo=2017-11-15", "/?bar2=foo", "", "") - testFormBindingForTimeFailFormat(t, "GET", + testFormBindingForTimeFailFormat(t, http.MethodGet, "/?time_foo=2017-11-15", "/?bar2=foo", "", "") - testFormBindingForTimeFailLocation(t, "GET", + testFormBindingForTimeFailLocation(t, http.MethodGet, "/?time_foo=2017-11-15", "/?bar2=foo", "", "") } func TestFormBindingIgnoreField(t *testing.T) { - testFormBindingIgnoreField(t, "POST", + testFormBindingIgnoreField(t, http.MethodPost, "/", "/", "-=bar", "") } func TestBindingFormInvalidName(t *testing.T) { - testFormBindingInvalidName(t, "POST", + testFormBindingInvalidName(t, http.MethodPost, "/", "/", "test_name=bar", "bar2=foo") } func TestBindingFormInvalidName2(t *testing.T) { - testFormBindingInvalidName2(t, "POST", + testFormBindingInvalidName2(t, http.MethodPost, "/", "/", "map_foo=bar", "bar2=foo") } func TestBindingFormForType(t *testing.T) { - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "map_foo={\"bar\":123}", "map_foo=1", "Map") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "slice_foo=1&slice_foo=2", "bar2=1&bar2=2", "Slice") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?slice_foo=1&slice_foo=2", "/?bar2=1&bar2=2", "", "", "Slice") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "slice_map_foo=1&slice_map_foo=2", "bar2=1&bar2=2", "SliceMap") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2", "", "", "SliceMap") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "ptr_bar=test", "bar2=test", "Ptr") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?ptr_bar=test", "/?bar2=test", "", "", "Ptr") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "idx=123", "id1=1", "Struct") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?idx=123", "/?id1=1", "", "", "Struct") - testFormBindingForType(t, "POST", + testFormBindingForType(t, http.MethodPost, "/", "/", "name=thinkerou", "name1=ou", "StructPointer") - testFormBindingForType(t, "GET", + testFormBindingForType(t, http.MethodGet, "/?name=thinkerou", "/?name1=ou", "", "", "StructPointer") } @@ -374,7 +374,7 @@ func TestBindingFormStringMap(t *testing.T) { func TestBindingFormStringSliceMap(t *testing.T) { obj := make(map[string][]string) - req := requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") + req := requestWithBody(http.MethodPost, "/", "foo=something&foo=bar&hello=world") req.Header.Add("Content-Type", MIMEPOSTForm) err := Form.Bind(req, &obj) require.NoError(t, err) @@ -387,38 +387,38 @@ func TestBindingFormStringSliceMap(t *testing.T) { assert.True(t, reflect.DeepEqual(obj, target)) objInvalid := make(map[string][]int) - req = requestWithBody("POST", "/", "foo=something&foo=bar&hello=world") + req = requestWithBody(http.MethodPost, "/", "foo=something&foo=bar&hello=world") req.Header.Add("Content-Type", MIMEPOSTForm) err = Form.Bind(req, &objInvalid) require.Error(t, err) } func TestBindingQuery(t *testing.T) { - testQueryBinding(t, "POST", + testQueryBinding(t, http.MethodPost, "/?foo=bar&bar=foo", "/", "foo=unused", "bar2=foo") } func TestBindingQuery2(t *testing.T) { - testQueryBinding(t, "GET", + testQueryBinding(t, http.MethodGet, "/?foo=bar&bar=foo", "/?bar2=foo", "foo=unused", "") } func TestBindingQueryFail(t *testing.T) { - testQueryBindingFail(t, "POST", + testQueryBindingFail(t, http.MethodPost, "/?map_foo=", "/", "map_foo=unused", "bar2=foo") } func TestBindingQueryFail2(t *testing.T) { - testQueryBindingFail(t, "GET", + testQueryBindingFail(t, http.MethodGet, "/?map_foo=", "/?bar2=foo", "map_foo=unused", "") } func TestBindingQueryBoolFail(t *testing.T) { - testQueryBindingBoolFail(t, "GET", + testQueryBindingBoolFail(t, http.MethodGet, "/?bool_foo=fasl", "/?bar2=foo", "bool_foo=unused", "") } @@ -427,7 +427,7 @@ func TestBindingQueryStringMap(t *testing.T) { b := Query obj := make(map[string]string) - req := requestWithBody("GET", "/?foo=bar&hello=world", "") + req := requestWithBody(http.MethodGet, "/?foo=bar&hello=world", "") err := b.Bind(req, &obj) require.NoError(t, err) assert.NotNil(t, obj) @@ -436,7 +436,7 @@ func TestBindingQueryStringMap(t *testing.T) { assert.Equal(t, "world", obj["hello"]) obj = make(map[string]string) - req = requestWithBody("GET", "/?foo=bar&foo=2&hello=world", "") // should pick last + req = requestWithBody(http.MethodGet, "/?foo=bar&foo=2&hello=world", "") // should pick last err = b.Bind(req, &obj) require.NoError(t, err) assert.NotNil(t, obj) @@ -495,28 +495,28 @@ func TestBindingYAMLFail(t *testing.T) { } func createFormPostRequest(t *testing.T) *http.Request { - req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) + req, err := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createDefaultFormPostRequest(t *testing.T) *http.Request { - req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) + req, err := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMap(t *testing.T) *http.Request { - req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) + req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req } func createFormPostRequestForMapFail(t *testing.T) *http.Request { - req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) + req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEPOSTForm) return req @@ -540,7 +540,7 @@ func createFormFilesMultipartRequest(t *testing.T) *http.Request { _, err = io.Copy(fw, f) require.NoError(t, err) - req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + req, err2 := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", body) require.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -565,7 +565,7 @@ func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { _, err = io.Copy(fw, f) require.NoError(t, err) - req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + req, err2 := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", body) require.NoError(t, err2) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -581,7 +581,7 @@ func createFormMultipartRequest(t *testing.T) *http.Request { require.NoError(t, mw.SetBoundary(boundary)) require.NoError(t, mw.WriteField("foo", "bar")) require.NoError(t, mw.WriteField("bar", "foo")) - req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + req, err := http.NewRequest(http.MethodPost, "/?foo=getfoo&bar=getbar", body) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -595,7 +595,7 @@ func createFormMultipartRequestForMap(t *testing.T) *http.Request { require.NoError(t, mw.SetBoundary(boundary)) require.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) - req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) + req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", body) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -609,7 +609,7 @@ func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { require.NoError(t, mw.SetBoundary(boundary)) require.NoError(t, mw.WriteField("map_foo", "3.14")) - req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) + req, err := http.NewRequest(http.MethodPost, "/?map_foo=getfoo", body) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -731,7 +731,7 @@ func TestBindingProtoBufFail(t *testing.T) { func TestValidationFails(t *testing.T) { var obj FooStruct - req := requestWithBody("POST", "/", `{"bar": "foo"}`) + req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) require.Error(t, err) } @@ -742,7 +742,7 @@ func TestValidationDisabled(t *testing.T) { defer func() { Validator = backup }() var obj FooStruct - req := requestWithBody("POST", "/", `{"bar": "foo"}`) + req := requestWithBody(http.MethodPost, "/", `{"bar": "foo"}`) err := JSON.Bind(req, &obj) require.NoError(t, err) } @@ -753,7 +753,7 @@ func TestRequiredSucceeds(t *testing.T) { } var obj HogeStruct - req := requestWithBody("POST", "/", `{"hoge": 0}`) + req := requestWithBody(http.MethodPost, "/", `{"hoge": 0}`) err := JSON.Bind(req, &obj) require.NoError(t, err) } @@ -764,7 +764,7 @@ func TestRequiredFails(t *testing.T) { } var obj HogeStruct - req := requestWithBody("POST", "/", `{"boen": 0}`) + req := requestWithBody(http.MethodPost, "/", `{"boen": 0}`) err := JSON.Bind(req, &obj) require.Error(t, err) } @@ -778,12 +778,12 @@ func TestHeaderBinding(t *testing.T) { } var theader tHeader - req := requestWithBody("GET", "/", "") + req := requestWithBody(http.MethodGet, "/", "") req.Header.Add("limit", "1000") require.NoError(t, h.Bind(req, &theader)) assert.Equal(t, 1000, theader.Limit) - req = requestWithBody("GET", "/", "") + req = requestWithBody(http.MethodGet, "/", "") req.Header.Add("fail", `{fail:fail}`) type failStruct struct { @@ -843,7 +843,7 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba obj := QueryTest{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -859,7 +859,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) obj := FooBarStruct{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -879,7 +879,7 @@ func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badB obj := FooDefaultBarStruct{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -898,14 +898,14 @@ func TestFormBindingFail(t *testing.T) { assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} - req, _ := http.NewRequest("POST", "/", nil) + req, _ := http.NewRequest(http.MethodPost, "/", nil) err := b.Bind(req, &obj) require.Error(t, err) } func TestFormBindingMultipartFail(t *testing.T) { obj := FooBarStruct{} - req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar")) + req, err := http.NewRequest(http.MethodPost, "/", strings.NewReader("foo=bar")) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary") _, err = req.MultipartReader() @@ -919,7 +919,7 @@ func TestFormPostBindingFail(t *testing.T) { assert.Equal(t, "form-urlencoded", b.Name()) obj := FooBarStruct{} - req, _ := http.NewRequest("POST", "/", nil) + req, _ := http.NewRequest(http.MethodPost, "/", nil) err := b.Bind(req, &obj) require.Error(t, err) } @@ -929,7 +929,7 @@ func TestFormMultipartBindingFail(t *testing.T) { assert.Equal(t, "multipart/form-data", b.Name()) obj := FooBarStruct{} - req, _ := http.NewRequest("POST", "/", nil) + req, _ := http.NewRequest(http.MethodPost, "/", nil) err := b.Bind(req, &obj) require.Error(t, err) } @@ -940,7 +940,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForTimeType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -965,7 +965,7 @@ func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, bo obj := FooStructForTimeTypeNotUnixFormat{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -983,7 +983,7 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, obj := FooStructForTimeTypeNotFormat{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1001,7 +1001,7 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, obj := FooStructForTimeTypeFailFormat{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1019,7 +1019,7 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod obj := FooStructForTimeTypeFailLocation{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1037,7 +1037,7 @@ func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBo obj := FooStructForIgnoreFormTag{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1052,7 +1052,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo obj := InvalidNameType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1071,7 +1071,7 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB obj := InvalidNameMapType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1088,7 +1088,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, "form", b.Name()) req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } switch typ { @@ -1159,7 +1159,7 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) obj := FooBarStruct{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1174,7 +1174,7 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str obj := FooStructForMapType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1187,7 +1187,7 @@ func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody obj := FooStructForBoolType{} req := requestWithBody(method, path, body) - if method == "POST" { + if method == http.MethodPost { req.Header.Add("Content-Type", MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -1198,13 +1198,13 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody assert.Equal(t, name, b.Name()) obj := FooStruct{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj) require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) } @@ -1213,19 +1213,19 @@ func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, ba assert.Equal(t, name, b.Name()) var obj1 []FooStruct - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj1) require.NoError(t, err) var obj2 []FooStruct - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj2) require.Error(t, err) } func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) { obj := make(map[string]string) - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) if b.Name() == "form" { req.Header.Add("Content-Type", MIMEPOSTForm) } @@ -1238,13 +1238,13 @@ func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badB if badPath != "" && badBody != "" { obj = make(map[string]string) - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = b.Bind(req, &obj) require.Error(t, err) } objInt := make(map[string]int) - req = requestWithBody("POST", path, body) + req = requestWithBody(http.MethodPost, path, body) err = b.Bind(req, &objInt) require.Error(t, err) } @@ -1253,7 +1253,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) EnableDecoderUseNumber = true err := b.Bind(req, &obj) require.NoError(t, err) @@ -1263,7 +1263,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body assert.Equal(t, int64(123), v) obj = FooStructUseNumber{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) } @@ -1272,7 +1272,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) EnableDecoderUseNumber = false err := b.Bind(req, &obj) require.NoError(t, err) @@ -1281,7 +1281,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod assert.InDelta(t, float64(123), obj.Foo, 0.01) obj = FooStructUseNumber{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) } @@ -1293,13 +1293,13 @@ func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath }() obj := FooStructDisallowUnknownFields{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj) require.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStructDisallowUnknownFields{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) assert.Contains(t, err.Error(), "what") @@ -1309,13 +1309,13 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad assert.Equal(t, name, b.Name()) obj := FooStruct{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) err := b.Bind(req, &obj) require.Error(t, err) assert.Equal(t, "", obj.Foo) obj = FooStruct{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) err = JSON.Bind(req, &obj) require.Error(t, err) } @@ -1324,14 +1324,14 @@ func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, ba assert.Equal(t, name, b.Name()) obj := protoexample.Test{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) require.NoError(t, err) assert.Equal(t, "yes", *obj.Label) obj = protoexample.Test{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) require.Error(t, err) @@ -1358,28 +1358,28 @@ func TestPlainBinding(t *testing.T) { assert.Equal(t, "plain", p.Name()) var s string - req := requestWithBody("POST", "/", "test string") + req := requestWithBody(http.MethodPost, "/", "test string") require.NoError(t, p.Bind(req, &s)) assert.Equal(t, "test string", s) var bs []byte - req = requestWithBody("POST", "/", "test []byte") + req = requestWithBody(http.MethodPost, "/", "test []byte") require.NoError(t, p.Bind(req, &bs)) assert.Equal(t, bs, []byte("test []byte")) var i int - req = requestWithBody("POST", "/", "test fail") + req = requestWithBody(http.MethodPost, "/", "test fail") require.Error(t, p.Bind(req, &i)) - req = requestWithBody("POST", "/", "") + req = requestWithBody(http.MethodPost, "/", "") req.Body = &failRead{} require.Error(t, p.Bind(req, &s)) - req = requestWithBody("POST", "/", "") + req = requestWithBody(http.MethodPost, "/", "") require.NoError(t, p.Bind(req, nil)) var ptr *string - req = requestWithBody("POST", "/", "") + req = requestWithBody(http.MethodPost, "/", "") require.NoError(t, p.Bind(req, ptr)) } @@ -1387,7 +1387,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body assert.Equal(t, name, b.Name()) obj := protoexample.Test{} - req := requestWithBody("POST", path, body) + req := requestWithBody(http.MethodPost, path, body) req.Body = io.NopCloser(&hook{}) req.Header.Add("Content-Type", MIMEPROTOBUF) @@ -1402,7 +1402,7 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body assert.Equal(t, "obj is not ProtoMessage", err.Error()) obj = protoexample.Test{} - req = requestWithBody("POST", badPath, badBody) + req = requestWithBody(http.MethodPost, badPath, badBody) req.Header.Add("Content-Type", MIMEPROTOBUF) err = ProtoBuf.Bind(req, &obj) require.Error(t, err) diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go index 9782b81..c93f214 100644 --- a/binding/multipart_form_mapping_test.go +++ b/binding/multipart_form_mapping_test.go @@ -116,7 +116,7 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request err := mw.Close() require.NoError(t, err) - req, err := http.NewRequest("POST", "/", &body) + req, err := http.NewRequest(http.MethodPost, "/", &body) require.NoError(t, err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary()) diff --git a/context_test.go b/context_test.go index 7c41484..91d5e89 100644 --- a/context_test.go +++ b/context_test.go @@ -60,7 +60,7 @@ func createMultipartRequest() *http.Request { must(mw.WriteField("time_location", "31/12/2016 14:55")) must(mw.WriteField("names[a]", "thinkerou")) must(mw.WriteField("names[b]", "tianou")) - req, err := http.NewRequest("POST", "/", body) + req, err := http.NewRequest(http.MethodPost, "/", body) must(err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -81,7 +81,7 @@ func TestContextFormFile(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") require.NoError(t, err) @@ -95,7 +95,7 @@ func TestContextFormFileFailed(t *testing.T) { mw := multipart.NewWriter(buf) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) c.engine.MaxMultipartMemory = 8 << 20 f, err := c.FormFile("file") @@ -113,7 +113,7 @@ func TestContextMultipartForm(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.MultipartForm() require.NoError(t, err) @@ -128,7 +128,7 @@ func TestSaveUploadedOpenFailed(t *testing.T) { mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f := &multipart.FileHeader{ @@ -146,7 +146,7 @@ func TestSaveUploadedCreateFailed(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") require.NoError(t, err) @@ -481,7 +481,7 @@ func TestContextGetStringMapStringSlice(t *testing.T) { func TestContextCopy(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.index = 2 - c.Request, _ = http.NewRequest("POST", "/hola", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/hola", nil) c.handlers = HandlersChain{func(c *Context) {}} c.Params = Params{Param{Key: "foo", Value: "bar"}} c.Set("foo", "bar") @@ -538,7 +538,7 @@ func TestContextHandler(t *testing.T) { func TestContextQuery(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "http://example.com/?foo=bar&page=10&id=", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "http://example.com/?foo=bar&page=10&id=", nil) value, ok := c.GetQuery("foo") assert.True(t, ok) @@ -631,7 +631,7 @@ func TestContextDefaultQueryOnEmptyRequest(t *testing.T) { func TestContextQueryAndPostForm(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") - c.Request, _ = http.NewRequest("POST", + c.Request, _ = http.NewRequest(http.MethodPost, "/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) @@ -651,7 +651,7 @@ func TestContextQueryAndPostForm(t *testing.T) { assert.Empty(t, value) assert.Empty(t, c.PostForm("both")) assert.Empty(t, c.DefaultPostForm("both", "nothing")) - assert.Equal(t, "GET", c.Query("both"), "GET") + assert.Equal(t, http.MethodGet, c.Query("both"), http.MethodGet) value, ok = c.GetQuery("id") assert.True(t, ok) @@ -699,7 +699,7 @@ func TestContextQueryAndPostForm(t *testing.T) { values = c.QueryArray("both") assert.Len(t, values, 1) - assert.Equal(t, "GET", values[0]) + assert.Equal(t, http.MethodGet, values[0]) dicts, ok := c.GetQueryMap("ids") assert.True(t, ok) @@ -834,7 +834,7 @@ func TestContextSetCookiePathEmpty(t *testing.T) { func TestContextGetCookie(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "/get", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/get", nil) c.Request.Header.Set("Cookie", "user=gin") cookie, _ := c.Cookie("user") assert.Equal(t, "gin", cookie) @@ -886,7 +886,7 @@ func TestContextRenderJSON(t *testing.T) { func TestContextRenderJSONP(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "http://example.com/?callback=x", nil) c.JSONP(http.StatusCreated, H{"foo": "bar"}) @@ -900,7 +900,7 @@ func TestContextRenderJSONP(t *testing.T) { func TestContextRenderJSONPWithoutCallback(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("GET", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "http://example.com", nil) c.JSONP(http.StatusCreated, H{"foo": "bar"}) @@ -1043,7 +1043,7 @@ func TestContextRenderHTML2(t *testing.T) { c, router := CreateTestContext(w) // print debug warning log when Engine.trees > 0 - router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodGet, "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 1) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) @@ -1199,7 +1199,7 @@ func TestContextRenderFile(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("GET", "/", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c.File("./gin.go") assert.Equal(t, http.StatusOK, w.Code) @@ -1213,7 +1213,7 @@ func TestContextRenderFileFromFS(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("GET", "/some/path", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/some/path", nil) c.FileFromFS("./gin.go", Dir(".", false)) assert.Equal(t, http.StatusOK, w.Code) @@ -1229,7 +1229,7 @@ func TestContextRenderAttachment(t *testing.T) { c, _ := CreateTestContext(w) newFilename := "new_filename.go" - c.Request, _ = http.NewRequest("GET", "/", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c.FileAttachment("./gin.go", newFilename) assert.Equal(t, 200, w.Code) @@ -1243,7 +1243,7 @@ func TestContextRenderAndEscapeAttachment(t *testing.T) { maliciousFilename := "tampering_field.sh\"; \\\"; dummy=.go" actualEscapedResponseFilename := "tampering_field.sh\\\"; \\\\\\\"; dummy=.go" - c.Request, _ = http.NewRequest("GET", "/", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c.FileAttachment("./gin.go", maliciousFilename) assert.Equal(t, 200, w.Code) @@ -1256,7 +1256,7 @@ func TestContextRenderUTF8Attachment(t *testing.T) { c, _ := CreateTestContext(w) newFilename := "new🧡_filename.go" - c.Request, _ = http.NewRequest("GET", "/", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/", nil) c.FileAttachment("./gin.go", newFilename) assert.Equal(t, 200, w.Code) @@ -1335,7 +1335,7 @@ func TestContextRenderRedirectWithRelativePath(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) assert.Panics(t, func() { c.Redirect(299, "/new_path") }) assert.Panics(t, func() { c.Redirect(309, "/new_path") }) @@ -1349,7 +1349,7 @@ func TestContextRenderRedirectWithAbsolutePath(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) c.Redirect(http.StatusFound, "http://google.com") c.Writer.WriteHeaderNow() @@ -1361,7 +1361,7 @@ func TestContextRenderRedirectWith201(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) c.Redirect(http.StatusCreated, "/resource") c.Writer.WriteHeaderNow() @@ -1371,7 +1371,7 @@ func TestContextRenderRedirectWith201(t *testing.T) { func TestContextRenderRedirectAll(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "http://example.com", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", nil) assert.Panics(t, func() { c.Redirect(http.StatusOK, "/resource") }) assert.Panics(t, func() { c.Redirect(http.StatusAccepted, "/resource") }) assert.Panics(t, func() { c.Redirect(299, "/resource") }) @@ -1383,7 +1383,7 @@ func TestContextRenderRedirectAll(t *testing.T) { func TestContextNegotiationWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEJSON, MIMEXML, MIMEYAML, MIMEYAML2}, @@ -1398,7 +1398,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { func TestContextNegotiationWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2}, @@ -1413,7 +1413,7 @@ func TestContextNegotiationWithXML(t *testing.T) { func TestContextNegotiationWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML, MIMEYAML2}, @@ -1428,7 +1428,7 @@ func TestContextNegotiationWithYAML(t *testing.T) { func TestContextNegotiationWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2}, @@ -1443,7 +1443,7 @@ func TestContextNegotiationWithTOML(t *testing.T) { func TestContextNegotiationWithHTML(t *testing.T) { w := httptest.NewRecorder() c, router := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) router.SetHTMLTemplate(templ) @@ -1461,7 +1461,7 @@ func TestContextNegotiationWithHTML(t *testing.T) { func TestContextNegotiationNotSupport(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) c.Negotiate(http.StatusOK, Negotiate{ Offered: []string{MIMEPOSTForm}, @@ -1474,7 +1474,7 @@ func TestContextNegotiationNotSupport(t *testing.T) { func TestContextNegotiationFormat(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "", nil) assert.Panics(t, func() { c.NegotiateFormat() }) assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) @@ -1483,7 +1483,7 @@ func TestContextNegotiationFormat(t *testing.T) { func TestContextNegotiationFormatWithAccept(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML)) @@ -1493,7 +1493,7 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) { func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "*/*") assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) @@ -1504,7 +1504,7 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML)) c, _ = CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "text/*") assert.Equal(t, "*/*", c.NegotiateFormat("*/*")) @@ -1517,7 +1517,7 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { func TestContextNegotiationFormatCustom(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") c.Accepted = nil @@ -1530,7 +1530,7 @@ func TestContextNegotiationFormatCustom(t *testing.T) { func TestContextNegotiationFormat2(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("Accept", "image/tiff-fx") assert.Equal(t, "", c.NegotiateFormat("image/tiff")) @@ -1658,7 +1658,7 @@ func TestContextAbortWithError(t *testing.T) { func TestContextClientIP(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs() resetContextForClientIPTests(c) @@ -1801,7 +1801,7 @@ func resetContextForClientIPTests(c *Context) { func TestContextContentType(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Set("Content-Type", "application/json; charset=utf-8") assert.Equal(t, "application/json", c.ContentType()) @@ -1809,7 +1809,7 @@ func TestContextContentType(t *testing.T) { func TestContextAutoBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { @@ -1826,7 +1826,7 @@ func TestContextBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1843,7 +1843,7 @@ func TestContextBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(` FOO BAR @@ -1864,7 +1864,7 @@ func TestContextBindPlain(t *testing.T) { // string w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(`test string`)) c.Request.Header.Add("Content-Type", MIMEPlain) var s string @@ -1874,7 +1874,7 @@ func TestContextBindPlain(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) // []byte - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(`test []byte`)) c.Request.Header.Add("Content-Type", MIMEPlain) var bs []byte @@ -1888,7 +1888,7 @@ func TestContextBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("rate", "8000") c.Request.Header.Add("domain", "music") c.Request.Header.Add("limit", "1000") @@ -1910,7 +1910,7 @@ func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) var obj struct { Foo string `form:"foo"` @@ -1926,7 +1926,7 @@ func TestContextBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1943,7 +1943,7 @@ func TestContextBindWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo = 'bar'\nbar = 'foo'")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo = 'bar'\nbar = 'foo'")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -1960,7 +1960,7 @@ func TestContextBadAutoBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` @@ -1979,7 +1979,7 @@ func TestContextBadAutoBind(t *testing.T) { func TestContextAutoShouldBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { @@ -1996,7 +1996,7 @@ func TestContextShouldBindWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2013,7 +2013,7 @@ func TestContextShouldBindWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(` FOO BAR @@ -2034,7 +2034,7 @@ func TestContextShouldBindPlain(t *testing.T) { // string w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(`test string`)) c.Request.Header.Add("Content-Type", MIMEPlain) var s string @@ -2044,7 +2044,7 @@ func TestContextShouldBindPlain(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) // []byte - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(`test []byte`)) c.Request.Header.Add("Content-Type", MIMEPlain) var bs []byte @@ -2058,7 +2058,7 @@ func TestContextShouldBindHeader(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.Header.Add("rate", "8000") c.Request.Header.Add("domain", "music") c.Request.Header.Add("limit", "1000") @@ -2080,7 +2080,7 @@ func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) var obj struct { Foo string `form:"foo"` @@ -2100,7 +2100,7 @@ func TestContextShouldBindWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo: bar\nbar: foo")) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type var obj struct { @@ -2117,7 +2117,7 @@ func TestContextShouldBindWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'")) c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type var obj struct { @@ -2134,7 +2134,7 @@ func TestContextBadAutoShouldBind(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request.Header.Add("Content-Type", MIMEJSON) var obj struct { Foo string `json:"foo"` @@ -2198,7 +2198,7 @@ func TestContextShouldBindBodyWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( - "POST", "http://example.com", bytes.NewBufferString(tt.bodyA), + http.MethodPost, "http://example.com", bytes.NewBufferString(tt.bodyA), ) // When it binds to typeA and typeB, it finds the body is // not typeB but typeA. @@ -2216,7 +2216,7 @@ func TestContextShouldBindBodyWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) c.Request, _ = http.NewRequest( - "POST", "http://example.com", bytes.NewBufferString(tt.bodyB), + http.MethodPost, "http://example.com", bytes.NewBufferString(tt.bodyB), ) objA := typeA{} require.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) @@ -2263,7 +2263,7 @@ func TestContextShouldBindBodyWithJSON(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeJSON struct { Foo string `json:"foo" binding:"required"` @@ -2327,7 +2327,7 @@ func TestContextShouldBindBodyWithXML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeXML struct { Foo string `xml:"foo" binding:"required"` @@ -2391,7 +2391,7 @@ func TestContextShouldBindBodyWithYAML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeYAML struct { Foo string `yaml:"foo" binding:"required"` @@ -2456,7 +2456,7 @@ func TestContextShouldBindBodyWithTOML(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeTOML struct { Foo string `toml:"foo" binding:"required"` @@ -2525,7 +2525,7 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body)) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body)) type typeJSON struct { Foo string `json:"foo" binding:"required"` @@ -2562,7 +2562,7 @@ func TestContextShouldBindBodyWithPlain(t *testing.T) { func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request, _ = http.NewRequest(http.MethodPost, "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) require.NoError(t, c.Err()) assert.Nil(t, c.Done()) ti, ok := c.Deadline() @@ -2580,7 +2580,7 @@ func TestContextGolangContext(t *testing.T) { func TestWebsocketsRequired(t *testing.T) { // Example request from spec: https://tools.ietf.org/html/rfc6455#section-1.2 c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "/chat", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/chat", nil) c.Request.Header.Set("Host", "server.example.com") c.Request.Header.Set("Upgrade", "websocket") c.Request.Header.Set("Connection", "Upgrade") @@ -2593,7 +2593,7 @@ func TestWebsocketsRequired(t *testing.T) { // Normal request, no websocket required. c, _ = CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "/chat", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/chat", nil) c.Request.Header.Set("Host", "server.example.com") assert.False(t, c.IsWebsocket()) @@ -2601,7 +2601,7 @@ func TestWebsocketsRequired(t *testing.T) { func TestGetRequestHeaderValue(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("GET", "/chat", nil) + c.Request, _ = http.NewRequest(http.MethodGet, "/chat", nil) c.Request.Header.Set("Gin-Version", "1.0.0") assert.Equal(t, "1.0.0", c.GetHeader("Gin-Version")) @@ -2611,7 +2611,7 @@ func TestGetRequestHeaderValue(t *testing.T) { func TestContextGetRawData(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("Fetch binary post data") - c.Request, _ = http.NewRequest("POST", "/", body) + c.Request, _ = http.NewRequest(http.MethodPost, "/", body) c.Request.Header.Add("Content-Type", MIMEPOSTForm) data, err := c.GetRawData() @@ -2740,8 +2740,8 @@ func TestRaceParamsContextCopy(t *testing.T) { }(c.Copy(), c.Param("name")) }) } - PerformRequest(router, "GET", "/name1/api") - PerformRequest(router, "GET", "/name2/api") + PerformRequest(router, http.MethodGet, "/name1/api") + PerformRequest(router, http.MethodGet, "/name2/api") wg.Wait() } @@ -2760,7 +2760,7 @@ func TestContextWithKeysMutex(t *testing.T) { func TestRemoteIPFail(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request.RemoteAddr = "[:::]:80" ip := net.ParseIP(c.RemoteIP()) trust := c.engine.isTrustedProxy(ip) @@ -2862,7 +2862,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value")) return c, key }, @@ -2874,7 +2874,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value")) return c, contextKey("key") }, @@ -2897,7 +2897,7 @@ func TestContextWithFallbackValueFromRequestContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) // enable ContextWithFallback feature flag c.engine.ContextWithFallback = true - c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request, _ = http.NewRequest(http.MethodPost, "/", nil) return c, "key" }, value: nil, @@ -3029,7 +3029,7 @@ func TestInterceptedHeader(t *testing.T) { c.Header("X-Test-2", "present") c.String(http.StatusOK, "hello world") }) - c.Request = httptest.NewRequest("GET", "/", nil) + c.Request = httptest.NewRequest(http.MethodGet, "/", nil) r.HandleContext(c) // Result() has headers frozen when WriteHeaderNow() has been called // Compared to this time, this is when the response headers will be flushed diff --git a/debug_test.go b/debug_test.go index edf4bb1..0efbfd7 100644 --- a/debug_test.go +++ b/debug_test.go @@ -10,6 +10,7 @@ import ( "html/template" "io" "log" + "net/http" "os" "runtime" "strings" @@ -60,7 +61,7 @@ func TestDebugPrintError(t *testing.T) { func TestDebugPrintRoutes(t *testing.T) { re := captureOutput(t, func() { SetMode(DebugMode) - debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) + debugPrintRoute(http.MethodGet, "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) SetMode(TestMode) }) assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re) @@ -72,7 +73,7 @@ func TestDebugPrintRouteFunc(t *testing.T) { } re := captureOutput(t, func() { SetMode(DebugMode) - debugPrintRoute("GET", "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest}) + debugPrintRoute(http.MethodGet, "/path/to/route/:param1/:param2", HandlersChain{func(c *Context) {}, handlerNameTest}) SetMode(TestMode) }) assert.Regexp(t, `^\[GIN-debug\] GET /path/to/route/:param1/:param2 --> (.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest \(2 handlers\)\n$`, re) diff --git a/deprecated_test.go b/deprecated_test.go index 0240b2e..6c8f2a7 100644 --- a/deprecated_test.go +++ b/deprecated_test.go @@ -18,7 +18,7 @@ func TestBindWith(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest(http.MethodPost, "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) var obj struct { Foo string `form:"foo"` diff --git a/gin_test.go b/gin_test.go index 5d0c47d..732da18 100644 --- a/gin_test.go +++ b/gin_test.go @@ -327,31 +327,31 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) { func TestAddRoute(t *testing.T) { router := New() - router.addRoute("GET", "/", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodGet, "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 1) - assert.NotNil(t, router.trees.get("GET")) - assert.Nil(t, router.trees.get("POST")) + assert.NotNil(t, router.trees.get(http.MethodGet)) + assert.Nil(t, router.trees.get(http.MethodPost)) - router.addRoute("POST", "/", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodPost, "/", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 2) - assert.NotNil(t, router.trees.get("GET")) - assert.NotNil(t, router.trees.get("POST")) + assert.NotNil(t, router.trees.get(http.MethodGet)) + assert.NotNil(t, router.trees.get(http.MethodPost)) - router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}}) assert.Len(t, router.trees, 2) } func TestAddRouteFails(t *testing.T) { router := New() assert.Panics(t, func() { router.addRoute("", "/", HandlersChain{func(_ *Context) {}}) }) - assert.Panics(t, func() { router.addRoute("GET", "a", HandlersChain{func(_ *Context) {}}) }) - assert.Panics(t, func() { router.addRoute("GET", "/", HandlersChain{}) }) + assert.Panics(t, func() { router.addRoute(http.MethodGet, "a", HandlersChain{func(_ *Context) {}}) }) + assert.Panics(t, func() { router.addRoute(http.MethodGet, "/", HandlersChain{}) }) - router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}}) assert.Panics(t, func() { - router.addRoute("POST", "/post", HandlersChain{func(_ *Context) {}}) + router.addRoute(http.MethodPost, "/post", HandlersChain{func(_ *Context) {}}) }) } @@ -493,27 +493,27 @@ func TestListOfRoutes(t *testing.T) { assert.Len(t, list, 7) assertRoutePresent(t, list, RouteInfo{ - Method: "GET", + Method: http.MethodGet, Path: "/favicon.ico", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$", }) assertRoutePresent(t, list, RouteInfo{ - Method: "GET", + Method: http.MethodGet, Path: "/", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$", }) assertRoutePresent(t, list, RouteInfo{ - Method: "GET", + Method: http.MethodGet, Path: "/users/", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$", }) assertRoutePresent(t, list, RouteInfo{ - Method: "GET", + Method: http.MethodGet, Path: "/users/:id", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest1$", }) assertRoutePresent(t, list, RouteInfo{ - Method: "POST", + Method: http.MethodPost, Path: "/users/:id", Handler: "^(.*/vendor/)?github.com/gin-gonic/gin.handlerTest2$", }) @@ -531,7 +531,7 @@ func TestEngineHandleContext(t *testing.T) { } assert.NotPanics(t, func() { - w := PerformRequest(r, "GET", "/") + w := PerformRequest(r, http.MethodGet, "/") assert.Equal(t, 301, w.Code) }) } @@ -564,7 +564,7 @@ func TestEngineHandleContextManyReEntries(t *testing.T) { }) assert.NotPanics(t, func() { - w := PerformRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value + w := PerformRequest(r, http.MethodGet, "/"+strconv.Itoa(expectValue-1)) // include 0 value assert.Equal(t, 200, w.Code) assert.Equal(t, expectValue, w.Body.Len()) }) @@ -712,8 +712,8 @@ func TestNewOptionFunc(t *testing.T) { r := New(fc) routes := r.Routes() - assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) - assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest1"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest2"}) } func TestWithOptionFunc(t *testing.T) { @@ -729,8 +729,8 @@ func TestWithOptionFunc(t *testing.T) { }) routes := r.Routes() - assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"}) - assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest1"}) + assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest2"}) } type Birthday string @@ -749,7 +749,7 @@ func TestCustomUnmarshalStruct(t *testing.T) { _ = ctx.BindQuery(&request) ctx.JSON(200, request.Birthday) }) - req := httptest.NewRequest("GET", "/test?birthday=2000-01-01", nil) + req := httptest.NewRequest(http.MethodGet, "/test?birthday=2000-01-01", nil) w := httptest.NewRecorder() route.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) @@ -761,7 +761,7 @@ func TestMethodNotAllowedNoRoute(t *testing.T) { g := New() g.HandleMethodNotAllowed = true - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(http.MethodGet, "/", nil) resp := httptest.NewRecorder() assert.NotPanics(t, func() { g.ServeHTTP(resp, req) }) assert.Equal(t, http.StatusNotFound, resp.Code) diff --git a/logger_test.go b/logger_test.go index b05df74..de00c49 100644 --- a/logger_test.go +++ b/logger_test.go @@ -31,9 +31,9 @@ func TestLogger(t *testing.T) { router.HEAD("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {}) - PerformRequest(router, "GET", "/example?a=100") + PerformRequest(router, http.MethodGet, "/example?a=100") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") @@ -41,21 +41,21 @@ func TestLogger(t *testing.T) { // like integration tests because they test the whole logging process rather // than individual functions. Im not sure where these should go. buffer.Reset() - PerformRequest(router, "POST", "/example") + PerformRequest(router, http.MethodPost, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "POST") + assert.Contains(t, buffer.String(), http.MethodPost) assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "PUT", "/example") + PerformRequest(router, http.MethodPut, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "PUT") + assert.Contains(t, buffer.String(), http.MethodPut) assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "DELETE", "/example") + PerformRequest(router, http.MethodDelete, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "DELETE") + assert.Contains(t, buffer.String(), http.MethodDelete) assert.Contains(t, buffer.String(), "/example") buffer.Reset() @@ -77,9 +77,9 @@ func TestLogger(t *testing.T) { assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "GET", "/notfound") + PerformRequest(router, http.MethodGet, "/notfound") assert.Contains(t, buffer.String(), "404") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/notfound") } @@ -95,9 +95,9 @@ func TestLoggerWithConfig(t *testing.T) { router.HEAD("/example", func(c *Context) {}) router.OPTIONS("/example", func(c *Context) {}) - PerformRequest(router, "GET", "/example?a=100") + PerformRequest(router, http.MethodGet, "/example?a=100") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") @@ -105,21 +105,21 @@ func TestLoggerWithConfig(t *testing.T) { // like integration tests because they test the whole logging process rather // than individual functions. Im not sure where these should go. buffer.Reset() - PerformRequest(router, "POST", "/example") + PerformRequest(router, http.MethodPost, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "POST") + assert.Contains(t, buffer.String(), http.MethodPost) assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "PUT", "/example") + PerformRequest(router, http.MethodPut, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "PUT") + assert.Contains(t, buffer.String(), http.MethodPut) assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "DELETE", "/example") + PerformRequest(router, http.MethodDelete, "/example") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "DELETE") + assert.Contains(t, buffer.String(), http.MethodDelete) assert.Contains(t, buffer.String(), "/example") buffer.Reset() @@ -141,9 +141,9 @@ func TestLoggerWithConfig(t *testing.T) { assert.Contains(t, buffer.String(), "/example") buffer.Reset() - PerformRequest(router, "GET", "/notfound") + PerformRequest(router, http.MethodGet, "/notfound") assert.Contains(t, buffer.String(), "404") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/notfound") } @@ -169,12 +169,12 @@ func TestLoggerWithFormatter(t *testing.T) { ) })) router.GET("/example", func(c *Context) {}) - PerformRequest(router, "GET", "/example?a=100") + PerformRequest(router, http.MethodGet, "/example?a=100") // output test assert.Contains(t, buffer.String(), "[FORMATTER TEST]") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") } @@ -210,12 +210,12 @@ func TestLoggerWithConfigFormatting(t *testing.T) { gotKeys = c.Keys time.Sleep(time.Millisecond) }) - PerformRequest(router, "GET", "/example?a=100") + PerformRequest(router, http.MethodGet, "/example?a=100") // output test assert.Contains(t, buffer.String(), "[FORMATTER TEST]") assert.Contains(t, buffer.String(), "200") - assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), http.MethodGet) assert.Contains(t, buffer.String(), "/example") assert.Contains(t, buffer.String(), "a=100") @@ -225,7 +225,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) { assert.Equal(t, 200, gotParam.StatusCode) assert.NotEmpty(t, gotParam.Latency) assert.Equal(t, "20.20.20.20", gotParam.ClientIP) - assert.Equal(t, "GET", gotParam.Method) + assert.Equal(t, http.MethodGet, gotParam.Method) assert.Equal(t, "/example?a=100", gotParam.Path) assert.Empty(t, gotParam.ErrorMessage) assert.Equal(t, gotKeys, gotParam.Keys) @@ -239,7 +239,7 @@ func TestDefaultLogFormatter(t *testing.T) { StatusCode: 200, Latency: time.Second * 5, ClientIP: "20.20.20.20", - Method: "GET", + Method: http.MethodGet, Path: "/", ErrorMessage: "", isTerm: false, @@ -250,7 +250,7 @@ func TestDefaultLogFormatter(t *testing.T) { StatusCode: 200, Latency: time.Second * 5, ClientIP: "20.20.20.20", - Method: "GET", + Method: http.MethodGet, Path: "/", ErrorMessage: "", isTerm: true, @@ -260,7 +260,7 @@ func TestDefaultLogFormatter(t *testing.T) { StatusCode: 200, Latency: time.Millisecond * 9876543210, ClientIP: "20.20.20.20", - Method: "GET", + Method: http.MethodGet, Path: "/", ErrorMessage: "", isTerm: true, @@ -271,7 +271,7 @@ func TestDefaultLogFormatter(t *testing.T) { StatusCode: 200, Latency: time.Millisecond * 9876543210, ClientIP: "20.20.20.20", - Method: "GET", + Method: http.MethodGet, Path: "/", ErrorMessage: "", isTerm: false, @@ -292,10 +292,10 @@ func TestColorForMethod(t *testing.T) { return p.MethodColor() } - assert.Equal(t, blue, colorForMethod("GET"), "get should be blue") - assert.Equal(t, cyan, colorForMethod("POST"), "post should be cyan") - assert.Equal(t, yellow, colorForMethod("PUT"), "put should be yellow") - assert.Equal(t, red, colorForMethod("DELETE"), "delete should be red") + assert.Equal(t, blue, colorForMethod(http.MethodGet), "get should be blue") + assert.Equal(t, cyan, colorForMethod(http.MethodPost), "post should be cyan") + assert.Equal(t, yellow, colorForMethod(http.MethodPut), "put should be yellow") + assert.Equal(t, red, colorForMethod(http.MethodDelete), "delete should be red") assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green") assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta") assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white") @@ -369,15 +369,15 @@ func TestErrorLogger(t *testing.T) { c.String(http.StatusInternalServerError, "hola!") }) - w := PerformRequest(router, "GET", "/error") + w := PerformRequest(router, http.MethodGet, "/error") assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) - w = PerformRequest(router, "GET", "/abort") + w = PerformRequest(router, http.MethodGet, "/abort") assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) - w = PerformRequest(router, "GET", "/print") + w = PerformRequest(router, http.MethodGet, "/print") assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } @@ -389,11 +389,11 @@ func TestLoggerWithWriterSkippingPaths(t *testing.T) { router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) - PerformRequest(router, "GET", "/logged") + PerformRequest(router, http.MethodGet, "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() - PerformRequest(router, "GET", "/skipped") + PerformRequest(router, http.MethodGet, "/skipped") assert.Contains(t, buffer.String(), "") } @@ -407,11 +407,11 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) - PerformRequest(router, "GET", "/logged") + PerformRequest(router, http.MethodGet, "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() - PerformRequest(router, "GET", "/skipped") + PerformRequest(router, http.MethodGet, "/skipped") assert.Contains(t, buffer.String(), "") } @@ -427,11 +427,11 @@ func TestLoggerWithConfigSkipper(t *testing.T) { router.GET("/logged", func(c *Context) { c.Status(http.StatusOK) }) router.GET("/skipped", func(c *Context) { c.Status(http.StatusNoContent) }) - PerformRequest(router, "GET", "/logged") + PerformRequest(router, http.MethodGet, "/logged") assert.Contains(t, buffer.String(), "200") buffer.Reset() - PerformRequest(router, "GET", "/skipped") + PerformRequest(router, http.MethodGet, "/skipped") assert.Contains(t, buffer.String(), "") } diff --git a/middleware_test.go b/middleware_test.go index acdf89c..eafc60a 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -35,7 +35,7 @@ func TestMiddlewareGeneralCase(t *testing.T) { signature += " XX " }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusOK, w.Code) @@ -71,7 +71,7 @@ func TestMiddlewareNoRoute(t *testing.T) { signature += " X " }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusNotFound, w.Code) @@ -108,7 +108,7 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) { signature += " XX " }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusMethodNotAllowed, w.Code) @@ -149,7 +149,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) { }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusNotFound, w.Code) @@ -175,7 +175,7 @@ func TestMiddlewareAbort(t *testing.T) { }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusUnauthorized, w.Code) @@ -196,7 +196,7 @@ func TestMiddlewareAbortHandlersChainAndNext(t *testing.T) { c.Next() }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusGone, w.Code) @@ -219,7 +219,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { signature += "C" }) // RUN - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) @@ -246,7 +246,7 @@ func TestMiddlewareWrite(t *testing.T) { }) }) - w := PerformRequest(router, "GET", "/") + w := PerformRequest(router, http.MethodGet, "/") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) diff --git a/recovery_test.go b/recovery_test.go index ee063cd..08eec1e 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -25,7 +25,7 @@ func TestPanicClean(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery", + w := PerformRequest(router, http.MethodGet, "/recovery", header{ Key: "Host", Value: "www.google.com", @@ -55,7 +55,7 @@ func TestPanicInHandler(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -66,7 +66,7 @@ func TestPanicInHandler(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = PerformRequest(router, "GET", "/recovery") + w = PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") @@ -83,7 +83,7 @@ func TestPanicWithAbort(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) } @@ -134,7 +134,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { panic(e) }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, expectCode, w.Code) assert.Contains(t, strings.ToLower(buf.String()), expectMsg) @@ -155,7 +155,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -166,7 +166,7 @@ func TestCustomRecoveryWithWriter(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = PerformRequest(router, "GET", "/recovery") + w = PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") @@ -190,7 +190,7 @@ func TestCustomRecovery(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -201,7 +201,7 @@ func TestCustomRecovery(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = PerformRequest(router, "GET", "/recovery") + w = PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") @@ -225,7 +225,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { panic("Oupps, Houston, we have a problem") }) // RUN - w := PerformRequest(router, "GET", "/recovery") + w := PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "panic recovered") @@ -236,7 +236,7 @@ func TestRecoveryWithWriterWithCustomRecovery(t *testing.T) { // Debug mode prints the request SetMode(DebugMode) // RUN - w = PerformRequest(router, "GET", "/recovery") + w = PerformRequest(router, http.MethodGet, "/recovery") // TEST assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") diff --git a/render/render_test.go b/render/render_test.go index 27a5065..ad633b0 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -369,7 +369,7 @@ func TestRenderXML(t *testing.T) { } func TestRenderRedirect(t *testing.T) { - req, err := http.NewRequest("GET", "/test-redirect", nil) + req, err := http.NewRequest(http.MethodGet, "/test-redirect", nil) require.NoError(t, err) data1 := Redirect{ diff --git a/routes_test.go b/routes_test.go index 49f355a..995ff51 100644 --- a/routes_test.go +++ b/routes_test.go @@ -523,8 +523,8 @@ func TestRouteNotAllowedEnabled3(t *testing.T) { w := PerformRequest(router, http.MethodPut, "/path") assert.Equal(t, http.StatusMethodNotAllowed, w.Code) allowed := w.Header().Get("Allow") - assert.Contains(t, allowed, "GET") - assert.Contains(t, allowed, "POST") + assert.Contains(t, allowed, http.MethodGet) + assert.Contains(t, allowed, http.MethodPost) } func TestRouteNotAllowedDisabled(t *testing.T) { @@ -557,7 +557,7 @@ func TestRouterNotFoundWithRemoveExtraSlash(t *testing.T) { {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { - w := PerformRequest(router, "GET", tr.route) + w := PerformRequest(router, http.MethodGet, tr.route) assert.Equal(t, tr.code, w.Code) if w.Code != http.StatusNotFound { assert.Equal(t, tr.location, w.Header().Get("Location")) @@ -786,6 +786,6 @@ func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) { v1.GET("/orgs/:id", handlerTest1) v1.DELETE("/orgs/:id", handlerTest1) - w := PerformRequest(r, "GET", "/base/v1/user/groups") + w := PerformRequest(r, http.MethodGet, "/base/v1/user/groups") assert.Equal(t, http.StatusNotFound, w.Code) } diff --git a/utils_test.go b/utils_test.go index af08996..8098c68 100644 --- a/utils_test.go +++ b/utils_test.go @@ -29,7 +29,7 @@ type testStruct struct { } func (t *testStruct) ServeHTTP(w http.ResponseWriter, req *http.Request) { - assert.Equal(t.T, "POST", req.Method) + assert.Equal(t.T, http.MethodPost, req.Method) assert.Equal(t.T, "/path", req.URL.Path) w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, "hello") @@ -39,17 +39,17 @@ func TestWrap(t *testing.T) { router := New() router.POST("/path", WrapH(&testStruct{t})) router.GET("/path2", WrapF(func(w http.ResponseWriter, req *http.Request) { - assert.Equal(t, "GET", req.Method) + assert.Equal(t, http.MethodGet, req.Method) assert.Equal(t, "/path2", req.URL.Path) w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "hola!") })) - w := PerformRequest(router, "POST", "/path") + w := PerformRequest(router, http.MethodPost, "/path") assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "hello", w.Body.String()) - w = PerformRequest(router, "GET", "/path2") + w = PerformRequest(router, http.MethodGet, "/path2") assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "hola!", w.Body.String()) } @@ -119,13 +119,13 @@ func TestBindMiddleware(t *testing.T) { called = true value = c.MustGet(BindKey).(*bindTestStruct) }) - PerformRequest(router, "GET", "/?foo=hola&bar=10") + PerformRequest(router, http.MethodGet, "/?foo=hola&bar=10") assert.True(t, called) assert.Equal(t, "hola", value.Foo) assert.Equal(t, 10, value.Bar) called = false - PerformRequest(router, "GET", "/?foo=hola&bar=1") + PerformRequest(router, http.MethodGet, "/?foo=hola&bar=1") assert.False(t, called) assert.Panics(t, func() { From e46bd521859fdfc83c508f1d42c92cb7f91e9fcb Mon Sep 17 00:00:00 2001 From: haesuo566 <102643523+haesuo566@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:54:06 +0900 Subject: [PATCH 898/912] refactor(context): add an optional permission parameter to the SaveUploadedFile method (#4068) (#4088) Co-authored-by: hso --- context.go | 13 +++++++++++-- context_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/context.go b/context.go index cab1452..c724daf 100644 --- a/context.go +++ b/context.go @@ -7,6 +7,7 @@ package gin import ( "errors" "io" + "io/fs" "log" "math" "mime/multipart" @@ -676,14 +677,22 @@ func (c *Context) MultipartForm() (*multipart.Form, error) { } // SaveUploadedFile uploads the form file to specific dst. -func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error { +func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm ...fs.FileMode) error { src, err := file.Open() if err != nil { return err } defer src.Close() - if err = os.MkdirAll(filepath.Dir(dst), 0o750); err != nil { + if len(perm) <= 0 { + perm = append(perm, 0o750) + } + + if err = os.MkdirAll(filepath.Dir(dst), perm[0]); err != nil { + return err + } + + if err = os.Chmod(filepath.Dir(dst), perm[0]); err != nil { return err } diff --git a/context_test.go b/context_test.go index 91d5e89..5b63a64 100644 --- a/context_test.go +++ b/context_test.go @@ -11,12 +11,14 @@ import ( "fmt" "html/template" "io" + "io/fs" "mime/multipart" "net" "net/http" "net/http/httptest" "net/url" "os" + "path/filepath" "reflect" "strconv" "strings" @@ -155,6 +157,45 @@ func TestSaveUploadedCreateFailed(t *testing.T) { require.Error(t, c.SaveUploadedFile(f, "/")) } +func TestSaveUploadedFileWithPermission(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + w, err := mw.CreateFormFile("file", "permission_test") + require.NoError(t, err) + _, err = w.Write([]byte("permission_test")) + require.NoError(t, err) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + f, err := c.FormFile("file") + require.NoError(t, err) + assert.Equal(t, "permission_test", f.Filename) + var mode fs.FileMode = 0o755 + require.NoError(t, c.SaveUploadedFile(f, "permission_test", mode)) + info, err := os.Stat(filepath.Dir("permission_test")) + require.NoError(t, err) + assert.Equal(t, info.Mode().Perm(), mode) +} + +func TestSaveUploadedFileWithPermissionFailed(t *testing.T) { + buf := new(bytes.Buffer) + mw := multipart.NewWriter(buf) + w, err := mw.CreateFormFile("file", "permission_test") + require.NoError(t, err) + _, err = w.Write([]byte("permission_test")) + require.NoError(t, err) + mw.Close() + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request.Header.Set("Content-Type", mw.FormDataContentType()) + f, err := c.FormFile("file") + require.NoError(t, err) + assert.Equal(t, "permission_test", f.Filename) + var mode fs.FileMode = 0o644 + require.Error(t, c.SaveUploadedFile(f, "test/permission_test", mode)) +} + func TestContextReset(t *testing.T) { router := New() c := router.allocateContext(0) From e2e80f33472bd02094f242da3c3efde2cec0a037 Mon Sep 17 00:00:00 2001 From: Xianglin Gao Date: Sat, 28 Dec 2024 17:18:03 +0800 Subject: [PATCH 899/912] chore(security): update vendor to fix CVE (#4121) Signed-off-by: Xianglin Gao --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 035c2de..5de7d06 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/quic-go/quic-go v0.43.1 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.27.0 + golang.org/x/net v0.33.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -39,10 +39,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect ) diff --git a/go.sum b/go.sum index 55a2162..d0389bb 100644 --- a/go.sum +++ b/go.sum @@ -92,22 +92,22 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= From 23d6961aeb9d2670a7b36c77cb180f479e220580 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 30 Dec 2024 11:39:24 +0800 Subject: [PATCH 900/912] ci(lint): update workflows and improve test request consistency (#4126) - Update GoReleaser action to version 6 in GitHub workflow - Use `http.MethodPost` constant in test requests instead of hardcoded string Signed-off-by: appleboy --- .github/workflows/goreleaser.yml | 2 +- context_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 8ae1182..22edf45 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -21,7 +21,7 @@ jobs: with: go-version: "^1" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v6 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser diff --git a/context_test.go b/context_test.go index 5b63a64..ef0cfcc 100644 --- a/context_test.go +++ b/context_test.go @@ -166,7 +166,7 @@ func TestSaveUploadedFileWithPermission(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") require.NoError(t, err) @@ -187,7 +187,7 @@ func TestSaveUploadedFileWithPermissionFailed(t *testing.T) { require.NoError(t, err) mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request, _ = http.NewRequest(http.MethodPost, "/", buf) c.Request.Header.Set("Content-Type", mw.FormDataContentType()) f, err := c.FormFile("file") require.NoError(t, err) From 3f818c3fa69e03feb46d2b49d2a8084c425cbed6 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Mon, 30 Dec 2024 11:40:37 +0800 Subject: [PATCH 901/912] chore(security): upgrade quic-go version to 0.48.2 (#4127) - Update Go versions in GitHub Actions workflow to `1.22` and `1.23` - Update README to require Go version `1.22` or above - Adjust table formatting in README for better alignment - Update warning message in `debug.go` to reflect Go version `1.22` - Update test in `debug_test.go` to reflect Go version `1.22` - Update `go.mod` to require Go version `1.22` - Update dependencies in `go.mod` to newer versions Signed-off-by: appleboy --- .github/workflows/gin.yml | 2 +- README.md | 4 ++-- debug.go | 2 +- debug_test.go | 2 +- go.mod | 9 ++++----- go.sum | 23 +++++++---------------- 6 files changed, 16 insertions(+), 26 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 74983c5..1d193ef 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.21", "1.22"] + go: ["1.22", "1.23"] test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: diff --git a/README.md b/README.md index 0464107..ae15504 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ If you need performance and good productivity, you will love Gin. ### Prerequisites -Gin requires [Go](https://go.dev/) version [1.21](https://go.dev/doc/devel/release#go1.21.0) or above. +Gin requires [Go](https://go.dev/) version [1.22](https://go.dev/doc/devel/release#go1.22.0) or above. ### Getting Gin @@ -113,7 +113,7 @@ The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in 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 | **43550** | **27364 ns/op** | **0 B/op** | **0 allocs/op** | | BenchmarkAce_GithubAll | 40543 | 29670 ns/op | 0 B/op | 0 allocs/op | | BenchmarkAero_GithubAll | 57632 | 20648 ns/op | 0 B/op | 0 allocs/op | diff --git a/debug.go b/debug.go index 62085c5..43dcd72 100644 --- a/debug.go +++ b/debug.go @@ -78,7 +78,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.21+. + debugPrint(`[WARNING] Now Gin requires Go 1.22+. `) } diff --git a/debug_test.go b/debug_test.go index 0efbfd7..4b440e3 100644 --- a/debug_test.go +++ b/debug_test.go @@ -106,7 +106,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.21+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.22+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/go.mod b/go.mod index 5de7d06..398787e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gin-gonic/gin -go 1.21.0 +go 1.22 require ( github.com/bytedance/sonic v1.11.6 @@ -10,7 +10,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml/v2 v2.2.2 - github.com/quic-go/quic-go v0.43.1 + github.com/quic-go/quic-go v0.48.2 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 golang.org/x/net v0.33.0 @@ -29,18 +29,17 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/go.sum b/go.sum index d0389bb..ddd0f87 100644 --- a/go.sum +++ b/go.sum @@ -9,7 +9,6 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -44,10 +43,6 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -62,15 +57,12 @@ github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ= -github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= +github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -94,8 +86,8 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VA golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= -golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= @@ -114,9 +106,8 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From c3c8620a7fb4e09c7845feca4e8e8a8678a2685d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 08:50:51 +0800 Subject: [PATCH 902/912] chore(deps): bump github.com/go-playground/validator/v10 from 10.20.0 to 10.22.1 (#4052) Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.20.0 to 10.22.1. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.20.0...v10.22.1) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 398787e..85ad56f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22 require ( github.com/bytedance/sonic v1.11.6 github.com/gin-contrib/sse v0.1.0 - github.com/go-playground/validator/v10 v10.20.0 + github.com/go-playground/validator/v10 v10.22.1 github.com/goccy/go-json v0.10.2 github.com/json-iterator/go v1.1.12 github.com/mattn/go-isatty v0.0.20 diff --git a/go.sum b/go.sum index ddd0f87..f4ab17c 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= From 3b28645dc95d58e0df36b8aff7a6c64f7c0ca5e9 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 12 Feb 2025 10:22:02 +0800 Subject: [PATCH 903/912] ci: add go version 1.24 to GitHub Actions (#4154) - Add Go version `1.24` to the GitHub Actions workflow Signed-off-by: Bo-Yi Wu --- .github/workflows/gin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 1d193ef..095dea6 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -33,7 +33,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.22", "1.23"] + go: ["1.22", "1.23", "1.24"] test-tags: ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] include: From 1eb827240e520804ea83b1aafcbaf7ba728b81dd Mon Sep 17 00:00:00 2001 From: NezhaFan Date: Tue, 18 Mar 2025 22:12:36 +0800 Subject: [PATCH 904/912] docs: fix case error of X-Real-IP (#4185) Co-authored-by: voyager1 --- CHANGELOG.md | 2 +- context.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de47c75..5648902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -488,7 +488,7 @@ - [FIX] Refactor render - [FIX] Reworked tests - [FIX] logger now supports cygwin -- [FIX] Use X-Forwarded-For before X-Real-Ip +- [FIX] Use X-Forwarded-For before X-Real-IP - [FIX] time.Time binding (#904) ## Gin 1.1.4 diff --git a/context.go b/context.go index c724daf..408f186 100644 --- a/context.go +++ b/context.go @@ -889,7 +889,7 @@ func (c *Context) ShouldBindBodyWithPlain(obj any) error { // ClientIP implements one best effort algorithm to return the real client IP. // It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. -// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). +// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-IP]). // If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy, // the remote IP (coming from Request.RemoteAddr) is returned. func (c *Context) ClientIP() string { From a4baac6e5e030ca707e519a3bf209d25699e3902 Mon Sep 17 00:00:00 2001 From: NezhaFan Date: Tue, 18 Mar 2025 22:14:38 +0800 Subject: [PATCH 905/912] refactor(context):Avoid using filepath.Dir twice in SaveUploadedFile (#4181) Co-authored-by: voyager1 --- context.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 408f186..1c76c0f 100644 --- a/context.go +++ b/context.go @@ -684,15 +684,15 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm } defer src.Close() - if len(perm) <= 0 { - perm = append(perm, 0o750) + var mode os.FileMode = 0o750 + if len(perm) > 0 { + mode = perm[0] } - - if err = os.MkdirAll(filepath.Dir(dst), perm[0]); err != nil { + dir := filepath.Dir(dst) + if err = os.MkdirAll(dir, mode); err != nil { return err } - - if err = os.Chmod(filepath.Dir(dst), perm[0]); err != nil { + if err = os.Chmod(dir, mode); err != nil { return err } From 733ee094fc4aaf016fb05820f553eeb0b81d0f1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 22:15:13 +0800 Subject: [PATCH 906/912] chore(deps): bump golang.org/x/net from 0.33.0 to 0.37.0 (#4178) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.37.0. - [Commits](https://github.com/golang/net/compare/v0.33.0...v0.37.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 9 +++++---- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 85ad56f..a8032b7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/gin-gonic/gin go 1.22 +toolchain go1.23.7 require ( github.com/bytedance/sonic v1.11.6 @@ -13,7 +14,7 @@ require ( github.com/quic-go/quic-go v0.48.2 github.com/stretchr/testify v1.9.0 github.com/ugorji/go/codec v1.2.12 - golang.org/x/net v0.33.0 + golang.org/x/net v0.37.0 google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -38,10 +39,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/crypto v0.31.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect ) diff --git a/go.sum b/go.sum index f4ab17c..d28753a 100644 --- a/go.sum +++ b/go.sum @@ -84,22 +84,22 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= From ebe5e2a6bfdca50fd44074b470ad486392e2933f Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Tue, 18 Mar 2025 23:13:03 +0800 Subject: [PATCH 907/912] fix(golangci.yml): move fiximports to goimports section and replace exportloopref with copyloopvar (#4167) Co-authored-by: huangzw --- .golangci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ccb2668..b50a911 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,7 +7,7 @@ linters: - durationcheck - errcheck - errorlint - - exportloopref + - copyloopvar - gci - gofmt - goimports @@ -39,10 +39,11 @@ linters-settings: perfsprint: err-error: true errorf: true - fiximports: true int-conversion: true sprintf1: true strconcat: true + goimports: + fiximports: true testifylint: enable-all: true From 90cf4602698dcbce18df3165b2d24e2940670a41 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Thu, 20 Mar 2025 10:13:47 +0800 Subject: [PATCH 908/912] chore: update Go versions and dependencies for improved compatibility (#4187) * chore: update Go versions and dependencies for improved compatibility - Update Go versions in workflow file to `1.23` and `1.24` - Enhance test tags in workflow with specific linker flags - Remove the conditional formatting step for Go `1.22.x` in workflow - Remove `goimports` settings from `.golangci.yml` - Update `go.mod` to use Go `1.23.0` - Upgrade `github.com/bytedance/sonic` from `v1.11.6` to `v1.13.1` - Update indirect dependencies `sonic/loader` to `v0.2.4` and `base64x` to `v0.1.5` in `go.mod` Signed-off-by: appleboy * chore: update project for Go 1.23 compatibility and documentation fixes - Update Go version requirement from 1.22 to 1.23 in README.md - Remove superfluous `$` from example command in README.md - Update warning message to reflect new Go version requirement in debug.go - Update test assertion to reflect new Go version requirement in debug_test.go Signed-off-by: appleboy --------- Signed-off-by: appleboy --- .github/workflows/gin.yml | 14 ++++++++------ .golangci.yml | 2 -- README.md | 4 ++-- debug.go | 2 +- debug_test.go | 2 +- go.mod | 10 ++++------ go.sum | 13 ++++++------- 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml index 095dea6..1062252 100644 --- a/.github/workflows/gin.yml +++ b/.github/workflows/gin.yml @@ -33,9 +33,15 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go: ["1.22", "1.23", "1.24"] + go: ["1.23", "1.24"] test-tags: - ["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"] + [ + "", + "-tags nomsgpack", + '--ldflags="-checklinkname=0" -tags "sonic avx"', + "-tags go_json", + "-race", + ] include: - os: ubuntu-latest go-build: ~/.cache/go-build @@ -75,7 +81,3 @@ jobs: uses: codecov/codecov-action@v4 with: flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }} - - - name: Format - if: matrix.go-version == '1.22.x' - run: diff -u <(echo -n) <(gofmt -d .) diff --git a/.golangci.yml b/.golangci.yml index b50a911..925e130 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -42,8 +42,6 @@ linters-settings: int-conversion: true sprintf1: true strconcat: true - goimports: - fiximports: true testifylint: enable-all: true diff --git a/README.md b/README.md index ae15504..fe5722b 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ If you need performance and good productivity, you will love Gin. ### Prerequisites -Gin requires [Go](https://go.dev/) version [1.22](https://go.dev/doc/devel/release#go1.22.0) or above. +Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above. ### Getting Gin @@ -73,7 +73,7 @@ func main() { To run the code, use the `go run` command, like: ```sh -$ go run example.go +go run example.go ``` Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response! diff --git a/debug.go b/debug.go index 43dcd72..f201616 100644 --- a/debug.go +++ b/debug.go @@ -78,7 +78,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.22+. + debugPrint(`[WARNING] Now Gin requires Go 1.23+. `) } diff --git a/debug_test.go b/debug_test.go index 4b440e3..59b61be 100644 --- a/debug_test.go +++ b/debug_test.go @@ -106,7 +106,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m < ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.22+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.23+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/go.mod b/go.mod index a8032b7..1223267 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,9 @@ module github.com/gin-gonic/gin -go 1.22 -toolchain go1.23.7 +go 1.23.0 require ( - github.com/bytedance/sonic v1.11.6 + github.com/bytedance/sonic v1.13.1 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.22.1 github.com/goccy/go-json v0.10.2 @@ -20,9 +19,8 @@ require ( ) require ( - github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-playground/locales v0.14.1 // indirect diff --git a/go.sum b/go.sum index d28753a..2e1c30a 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= +github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= +github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -112,4 +112,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From 4ccfa7c275c449990818e46759d5974a953cc1c1 Mon Sep 17 00:00:00 2001 From: takanuva15 <6986426+takanuva15@users.noreply.github.com> Date: Thu, 20 Mar 2025 11:33:10 -0400 Subject: [PATCH 909/912] feat(binding): add support for unixMilli and unixMicro (#4190) --- binding/binding_test.go | 26 ++++++++++++++++---------- binding/form_mapping.go | 16 +++++++++++----- docs/doc.md | 6 +++++- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 901e974..bdab369 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -69,15 +69,19 @@ type FooStructDisallowUnknownFields struct { } type FooBarStructForTimeType struct { - TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` - TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - UnixTime time.Time `form:"unixTime" time_format:"unix"` + TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` + TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` + UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixmilli"` + UnixMicroTime time.Time `form:"unixMicroTime" time_format:"uNiXmiCrO"` } type FooStructForTimeTypeNotUnixFormat struct { - CreateTime time.Time `form:"createTime" time_format:"unixNano"` - UnixTime time.Time `form:"unixTime" time_format:"unix"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` + UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixMilli"` + UnixMicroTime time.Time `form:"unixMicroTime" time_format:"unixMicro"` } type FooStructForTimeTypeNotFormat struct { @@ -265,10 +269,10 @@ func TestBindingFormDefaultValue2(t *testing.T) { func TestBindingFormForTime(t *testing.T) { testFormBindingForTime(t, http.MethodPost, "/", "/", - "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo") + "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012", "bar2=foo") testFormBindingForTimeNotUnixFormat(t, http.MethodPost, "/", "/", - "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") + "time_foo=2017-11-15&createTime=bad&unixTime=bad&unixMilliTime=bad&unixMicroTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, http.MethodPost, "/", "/", "time_foo=2017-11-15", "bar2=foo") @@ -282,11 +286,11 @@ func TestBindingFormForTime(t *testing.T) { func TestBindingFormForTime2(t *testing.T) { testFormBindingForTime(t, http.MethodGet, - "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo", + "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012", "/?bar2=foo", "", "") testFormBindingForTimeNotUnixFormat(t, http.MethodPost, "/", "/", - "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") + "time_foo=2017-11-15&createTime=bad&unixTime=bad&unixMilliTime=bad&unixMicroTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, http.MethodGet, "/?time_foo=2017-11-15", "/?bar2=foo", "", "") @@ -952,6 +956,8 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, "UTC", obj.TimeBar.Location().String()) assert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano()) assert.Equal(t, int64(1562400033), obj.UnixTime.Unix()) + assert.Equal(t, int64(1562400033001), obj.UnixMilliTime.UnixMilli()) + assert.Equal(t, int64(1562400033000012), obj.UnixMicroTime.UnixMicro()) obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index f5f6f3a..235692d 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -398,18 +398,24 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val } switch tf := strings.ToLower(timeFormat); tf { - case "unix", "unixnano": + case "unix", "unixmilli", "unixmicro", "unixnano": tv, err := strconv.ParseInt(val, 10, 64) if err != nil { return err } - d := time.Duration(1) - if tf == "unixnano" { - d = time.Second + var t time.Time + switch tf { + case "unix": + t = time.Unix(tv, 0) + case "unixmilli": + t = time.UnixMilli(tv) + case "unixmicro": + t = time.UnixMicro(tv) + default: + t = time.Unix(0, tv) } - t := time.Unix(tv/int64(d), tv%int64(d)) value.Set(reflect.ValueOf(t)) return nil } diff --git a/docs/doc.md b/docs/doc.md index a463e82..bea417b 100644 --- a/docs/doc.md +++ b/docs/doc.md @@ -832,6 +832,8 @@ type Person struct { Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` CreateTime time.Time `form:"createTime" time_format:"unixNano"` UnixTime time.Time `form:"unixTime" time_format:"unix"` + UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixmilli"` + UnixMicroTime time.Time `form:"unixMicroTime" time_format:"uNiXmIcRo"` // case does not matter for "unix*" time formats } func main() { @@ -851,6 +853,8 @@ func startPage(c *gin.Context) { log.Println(person.Birthday) log.Println(person.CreateTime) log.Println(person.UnixTime) + log.Println(person.UnixMilliTime) + log.Println(person.UnixMicroTime) } c.String(http.StatusOK, "Success") @@ -860,7 +864,7 @@ func startPage(c *gin.Context) { Test it with: ```sh -curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" +curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012" ``` From e737e3e267beb4dc3bab16cc8be59e3902d98a94 Mon Sep 17 00:00:00 2001 From: revevide <158151416+revevide@users.noreply.github.com> Date: Thu, 20 Mar 2025 23:35:49 +0800 Subject: [PATCH 910/912] fix(binding): prevent duplicate decoding and add validation in decodeToml (#4193) --- binding/toml.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binding/toml.go b/binding/toml.go index a66b93a..2681231 100644 --- a/binding/toml.go +++ b/binding/toml.go @@ -31,5 +31,5 @@ func decodeToml(r io.Reader, obj any) error { if err := decoder.Decode(obj); err != nil { return err } - return decoder.Decode(obj) + return validate(obj) } From 8763f33c65f7df8be5b9fe7504ab7fcf20abb41d Mon Sep 17 00:00:00 2001 From: bound2 <9380102+bound2@users.noreply.github.com> Date: Thu, 20 Mar 2025 17:40:41 +0200 Subject: [PATCH 911/912] fix: prevent middleware re-entry issue in HandleContext (#3987) --- gin.go | 2 ++ gin_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/gin.go b/gin.go index e17596a..0761c14 100644 --- a/gin.go +++ b/gin.go @@ -637,10 +637,12 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Disclaimer: You can loop yourself to deal with this, use wisely. func (engine *Engine) HandleContext(c *Context) { oldIndexValue := c.index + oldHandlers := c.handlers c.reset() engine.handleHTTPRequest(c) c.index = oldIndexValue + c.handlers = oldHandlers } func (engine *Engine) handleHTTPRequest(c *Context) { diff --git a/gin_test.go b/gin_test.go index 732da18..850ae09 100644 --- a/gin_test.go +++ b/gin_test.go @@ -573,6 +573,44 @@ func TestEngineHandleContextManyReEntries(t *testing.T) { assert.Equal(t, int64(expectValue), middlewareCounter) } +func TestEngineHandleContextPreventsMiddlewareReEntry(t *testing.T) { + // given + var handlerCounterV1, handlerCounterV2, middlewareCounterV1 int64 + + r := New() + v1 := r.Group("/v1") + { + v1.Use(func(c *Context) { + atomic.AddInt64(&middlewareCounterV1, 1) + }) + v1.GET("/test", func(c *Context) { + atomic.AddInt64(&handlerCounterV1, 1) + c.Status(http.StatusOK) + }) + } + + v2 := r.Group("/v2") + { + v2.GET("/test", func(c *Context) { + c.Request.URL.Path = "/v1/test" + r.HandleContext(c) + }, func(c *Context) { + atomic.AddInt64(&handlerCounterV2, 1) + }) + } + + // when + responseV1 := PerformRequest(r, "GET", "/v1/test") + responseV2 := PerformRequest(r, "GET", "/v2/test") + + // then + assert.Equal(t, 200, responseV1.Code) + assert.Equal(t, 200, responseV2.Code) + assert.Equal(t, int64(2), handlerCounterV1) + assert.Equal(t, int64(2), middlewareCounterV1) + assert.Equal(t, int64(1), handlerCounterV2) +} + func TestPrepareTrustedCIRDsWith(t *testing.T) { r := New() From 791efe6685e3edd8fb6613d3c70630d5d98ad4bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:05:32 +0000 Subject: [PATCH 912/912] chore(deps): bump github.com/bytedance/sonic from 1.13.1 to 1.13.2 Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.13.1 to 1.13.2. - [Release notes](https://github.com/bytedance/sonic/releases) - [Commits](https://github.com/bytedance/sonic/compare/v1.13.1...v1.13.2) --- updated-dependencies: - dependency-name: github.com/bytedance/sonic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 +++- go.sum | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1223267..3934ae1 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,10 @@ module github.com/gin-gonic/gin go 1.23.0 +toolchain go1.24.1 + require ( - github.com/bytedance/sonic v1.13.1 + github.com/bytedance/sonic v1.13.2 github.com/gin-contrib/sse v0.1.0 github.com/go-playground/validator/v10 v10.22.1 github.com/goccy/go-json v0.10.2 diff --git a/go.sum b/go.sum index 2e1c30a..9e32a33 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= -github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= +github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=