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 001/421] 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 3e817a78bb..e980edbc54 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 002/421] 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 6d40a5da4e..19e380fbcb 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 86a67773ca..9ace2989c2 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 003/421] 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 e980edbc54..10fb1d4506 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 07e7859f0b..028caebe25 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 004/421] 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 73bb770069..6710e42b2e 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 aaacf6c535..32c5b6684a 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 005/421] 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 32c5b6684a..ebf3b19924 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 af747a1e9a..a3a7bc4750 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 ada62ceb5e..07d6b4be58 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 006/421] 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 490e449008..e8dcd3dc3e 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 56bb3a00e3..9177e1d9fc 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 fca1c530b3..2ae9e889cd 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 18f27fa980..2b07cba09e 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 3aa5dbccb9..9d7eaeef01 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 007/421] Fix typo (#1913) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 10fb1d4506..2737e9ad69 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 008/421] 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 2737e9ad69..092e91ff4b 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 a3a7bc4750..425b627f29 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 4dbe983661..220f0401e3 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 e16c137637..457c923e2f 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 07d6b4be58..9a789f2f16 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 dbb0352bbe..e6e2886590 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 009/421] 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 425b627f29..3f1f8ca084 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 e8dcd3dc3e..89cfb4466a 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 010/421] 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 3f1f8ca084..64810d54d0 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 011/421] 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 64810d54d0..71337acdde 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 012/421] 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 71337acdde..ffb9a2de68 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 013/421] 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 1c5e995ccd..7680d4f744 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 5810468280..8610eae250 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 4de0bfd1ac..3e9d13b733 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 014/421] 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 89cfb4466a..b6ecb280eb 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 3e9d13b733..a225eb572a 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 015/421] Attempt to fix PostForm cache bug (#1931) --- context.go | 1 + 1 file changed, 1 insertion(+) diff --git a/context.go b/context.go index ffb9a2de68..77cdc182aa 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 016/421] 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 092e91ff4b..49b044aa91 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 0b28aa8a80..9e9fc3dedf 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 0000000000..f85a1aa604 --- /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 0000000000..4c75d1feb8 --- /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 017/421] 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 49b044aa91..aa5043aa4a 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 520c510976..6d58c3cdd1 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 6710e42b2e..827518f979 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 0000000000..179ce4ea20 --- /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 77cdc182aa..d9fcc28596 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 b6ecb280eb..439e8ee684 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 018/421] 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 aa5043aa4a..aa546e587a 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 019/421] modify readme example code (#1961) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa546e587a..f79c0c1cdb 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 020/421] 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 f6ec8a8209..27c80ef897 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 f79c0c1cdb..4257df3e4f 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 19e380fbcb..64777c2532 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 9ace2989c2..d6f320efcb 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 021/421] 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 457c923e2f..0c2f9a0ccf 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 9a789f2f16..371d5ad191 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 022/421] Add user case: brigade (#1937) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4257df3e4f..8d7705d354 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 023/421] 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 64777c2532..49080dbf5b 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 6070ff5579..25e8ff60ed 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 024/421] 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 8ea2495d41..15dfb1a8e8 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 025/421] Typo (#1971) --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index f4532d56cd..71b80de76f 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 026/421] 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 7680d4f744..dbe8afc5be 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 8610eae250..c1e9f2212f 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 a225eb572a..fa8fd13af4 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 027/421] 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 8d7705d354..2761259ec2 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 827518f979..806f3ac9d6 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 ebf3b19924..80b1d15a58 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 028/421] 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 220f0401e3..cbdd080e78 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 9d7eaeef01..acdb28ab47 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 029/421] 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 312af741d5..502d93987c 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 acdb28ab47..4cc71972c5 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 030/421] 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 9ed81b5dc5..c96b1e2950 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 197e9208bf..e44bd100ac 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 031/421] 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 dbe8afc5be..849f8c70ff 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 c1e9f2212f..de17ae7d03 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 032/421] 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 5ab4639efe..fcf90c2517 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 9177e1d9fc..fc53f3564c 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 033/421] 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 2761259ec2..529746a1b3 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 034/421] 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 439e8ee684..f7bb0f5162 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 2b07cba09e..70506f78a8 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 4cc71972c5..b27134ff3a 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 035/421] 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 27c80ef897..8b3b5a297d 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 036/421] 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 806f3ac9d6..3d08d69359 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 e7a302de67..50e0d57c93 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 2c76b6d6e5..81f78834f8 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 849f8c70ff..341518520e 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 de17ae7d03..7b4ee3209d 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 fa8fd13af4..d441d4a691 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 037/421] 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 529746a1b3..b488f159a6 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 038/421] 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 3d08d69359..caabaacee5 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 f968161b00..d62e070594 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 8aa84aa839..c3c37fdca2 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 3dba5150c7..0c5a3234a9 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 039/421] fix GetPostFormMap (#2051) --- context.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/context.go b/context.go index d9fcc28596..86094cac6e 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 040/421] 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 b27134ff3a..95a01b63e0 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 041/421] Make countParams more readable (#2052) --- tree.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tree.go b/tree.go index 371d5ad191..4194757050 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 042/421] 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 8b3b5a297d..748a07a768 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 043/421] 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 b488f159a6..dfedd8a29d 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 044/421] 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 86094cac6e..509ce08156 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 045/421] add TestDisableBindValidation (#2071) --- mode_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mode_test.go b/mode_test.go index 0c5a3234a9..1b5fb2ff03 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 046/421] 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 dfedd8a29d..959848dd59 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 047/421] 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 cbdd080e78..894cf0948c 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 9beec14d07..7e270b91a2 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 048/421] 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 a5e111e5c5..1f113e74d6 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 049/421] 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 15dfb1a8e8..6ccd2faf1b 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 959848dd59..22f83b65d7 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 050/421] 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 901d429c55..0000000000 --- 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 0000000000..cae4ccccdd --- /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 0000000000..6baa6739d2 --- /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 0000000000..f9546c1a78 --- /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 0000000000..e66338b7f0 --- /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 d441d4a691..70b2d9eb2a 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 051/421] Update go.sum file (#2094) --- go.sum | 1 + 1 file changed, 1 insertion(+) diff --git a/go.sum b/go.sum index 7b4ee3209d..129ad38707 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 052/421] 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 9d49aa4126..6f8288d58a 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 053/421] 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 509ce08156..18d328d277 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 054/421] 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 22f83b65d7..3dd99d9918 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 055/421] 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 80b1d15a58..ec78bfeed4 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 056/421] 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 caabaacee5..f0b6f79500 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 0ef08f006e..9572ea03dc 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 894cf0948c..58631263d0 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 7e270b91a2..d86f610b12 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 95a01b63e0..376733df5f 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 057/421] Update gin.go (#2110) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 58631263d0..caf4cf2c63 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 058/421] 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 a1e6c928b2..2e7a5b9070 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 059/421] [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 18d328d277..046f284ece 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 060/421] 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 ec78bfeed4..d6199c4fac 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 c9d6111baf..2a560371ca 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 061/421] 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 6ccd2faf1b..0bb90f2240 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 028caebe25..6f8235f9b9 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 062/421] 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 c3c37fdca2..edfc29406c 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 063/421] 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 748a07a768..4a4ab8179e 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 3dd99d9918..8aa50509c5 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 49080dbf5b..c66ca4408f 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 d6f320efcb..d707b4bfe7 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 064/421] 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 f7bb0f5162..18709d3dcd 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 502d93987c..d5282e4927 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 0000000000..3930f51df1 --- /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 065/421] 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 8aa50509c5..3f2d3c2c8e 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 066/421] 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 50e0d57c93..a4c1a7f6ce 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 81f78834f8..5299fbf602 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 341518520e..1213bd2355 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 129ad38707..9815f2f48b 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 067/421] 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 4a4ab8179e..b80b257714 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 51a6b9162c..e69dbd8b92 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 3f2d3c2c8e..012152a0e8 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 70b2d9eb2a..0000000000 --- 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 068/421] 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 9e9fc3dedf..b93c34cf42 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 caf4cf2c63..c194d6bf19 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 069/421] =?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 c194d6bf19..f0cd09e4d4 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 0c2f9a0ccf..d1ddbe9193 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 070/421] 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 6d58c3cdd1..f578aa55d2 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 f0cd09e4d4..71f3fd5cc8 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 d86f610b12..f29d1fc118 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 fb74d65940..925c5a141f 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 fcf90c2517..d5b96b3eae 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 2e7a5b9070..9ff7c03857 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 ce3d54a2f6..0e49d65b14 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 d1ddbe9193..ee6ea8234d 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 071/421] 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 9a7df86a3e..e4ff4677e2 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 072/421] 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 4194757050..1187f3f64a 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 073/421] 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 012152a0e8..f8bc423948 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 074/421] 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 1187f3f64a..b46ec828d8 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 e6e2886590..0fe2fe0011 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 075/421] tree: sync httprouter update (#2171) --- tree.go | 61 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/tree.go b/tree.go index b46ec828d8..ffd99896ca 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 076/421] tree: sync httprouter update (#2172) --- tree.go | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/tree.go b/tree.go index ffd99896ca..3a8a43559d 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 077/421] tree: sync httprouter update (#2173) --- tree.go | 156 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 85 insertions(+), 71 deletions(-) diff --git a/tree.go b/tree.go index 3a8a43559d..b09d3f67d7 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 078/421] 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 f8bc423948..7e3e6f418f 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 079/421] 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 b09d3f67d7..89f74deb12 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 080/421] 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 7e3e6f418f..01c4089a28 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 081/421] 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 01c4089a28..092fedc803 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 082/421] 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 092fedc803..5c77b28200 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 083/421] Remove broken link from README. (#2198) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5c77b28200..6e0ceb27ef 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 084/421] 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 0bb90f2240..1ceb919c1e 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 046f284ece..9be222fce2 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 085/421] 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 b80b257714..582b7329a7 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 e69dbd8b92..1a99193959 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 f578aa55d2..57562845fa 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 0000000000..9791a60763 --- /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 0000000000..fd227b11a8 --- /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 f0b6f79500..4424bab9cd 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 b7f7319771..a5bc2ad297 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 6baa6739d2..296d3eb18c 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 dc681fcf87..be2d45c51c 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 abfc79fcd6..bcd568bfba 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 0000000000..e439ac48ed --- /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 376733df5f..d0b56152c5 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 086/421] 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 d1f5962286..a6bac7bdfa 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 c1e6ed4f4e..caefd63a80 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 087/421] 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 9be222fce2..e979d28876 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 088/421] 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 a6bac7bdfa..37247d4181 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 089/421] =?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 c96b1e2950..9e5d4cf659 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 d6199c4fac..b81ad195ce 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 71f3fd5cc8..0244f18c9f 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 0000000000..32c2b59e08 --- /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 0000000000..ee2c8ab250 --- /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 70506f78a8..a6fd3117d3 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 090/421] 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 e979d28876..ee202d1e49 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 18709d3dcd..df2d9543f8 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 091/421] 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 1213bd2355..ae98b9e711 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 9815f2f48b..44c34a3806 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 092/421] 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 6e0ceb27ef..966ffbc579 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 093/421] 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 37247d4181..51346e4a05 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 89f74deb12..2f6b1890d4 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 094/421] 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 2f6b1890d4..b88eefa509 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 095/421] 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 ee202d1e49..1c1b963999 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 df2d9543f8..4380fb5b5d 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 096/421] 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 b88eefa509..b687ec43d5 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 097/421] 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 ae98b9e711..cfaee74622 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 44c34a3806..d499815524 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 098/421] 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 966ffbc579..4a0ec90cca 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 099/421] 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 4380fb5b5d..7f0bca3c9a 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 fc53f3564c..b587f89e23 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 2ae9e889cd..fca1c530b3 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 a6fd3117d3..015c0dbb55 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 d0b56152c5..353c82bbf2 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 100/421] ci support go1.14 (#2262) --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 582b7329a7..6680a5b360 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 101/421] 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 71b80de76f..77b5f0c8ab 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 102/421] 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 4a0ec90cca..709a15bc05 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 1c1b963999..1d3e665282 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 7f0bca3c9a..78b22c0d42 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 103/421] 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 d5b96b3eae..d361b74d32 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 b587f89e23..0d40666ec3 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 104/421] 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 0244f18c9f..ab1d0a4684 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 105/421] 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 1d3e665282..572dbb81bb 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 ab1d0a4684..1c2acbc8f4 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 106/421] =?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 709a15bc05..998783a37c 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 107/421] 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 1ceb919c1e..0fb00fa427 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 108/421] 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 6f8235f9b9..b024c50852 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 109/421] 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 77b5f0c8ab..71b80de76f 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 110/421] 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 b024c50852..ce6203c67e 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 111/421] 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 0fb00fa427..02459d512b 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 112/421] 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 572dbb81bb..5c1fab224a 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 78b22c0d42..80c7b4073a 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 113/421] 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 5c1fab224a..a2384c0eff 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 80c7b4073a..ce077bc691 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 d499815524..4c14fb83f7 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 114/421] 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 ce6203c67e..883bdad7d9 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 115/421] 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 998783a37c..9dde693d68 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 116/421] update:SetMode function (#2321) --- mode.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mode.go b/mode.go index edfc29406c..ca3677a9b0 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 117/421] 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 8630bc3561..e86bc98f82 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 547b777a1a..98d758ef1c 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 118/421] 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 02459d512b..c26b7658b9 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 119/421] 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 f29d1fc118..5f508c7090 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 120/421] 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 a2384c0eff..fb7f54e93d 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 1c2acbc8f4..ab1d0a4684 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 121/421] 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 c26b7658b9..592c2abc87 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 883bdad7d9..3e9687dc7b 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 122/421] 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 9e5d4cf659..43ad36f53b 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 fb7f54e93d..01cff1ae08 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 ab1d0a4684..888be36be2 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 ca3677a9b0..11f833e9a3 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 51346e4a05..d42d6b9d05 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 bc946c03ae..8cf0932a58 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 123/421] 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 01cff1ae08..4ebcc29411 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 888be36be2..4e72f81dbb 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 925c5a141f..3f440bceb8 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 fabd7b84ed..649a3cdb6a 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 ee6ea8234d..360ade6215 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 124/421] chore: update godoc address (#2357) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9dde693d68..dae63e2405 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 125/421] 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 e4ff4677e2..b164ae060d 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 dae63e2405..d75843a715 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 126/421] Fix typo (#2358) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d75843a715..5c145d5a74 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 127/421] uncomment the code (#2362) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5c145d5a74..9cc23fc2d6 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 128/421] 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 4e52d4c50d..30f5f53241 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 129/421] 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 4ebcc29411..2ea1a02869 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 4e72f81dbb..1e12617904 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 b687ec43d5..e3aa9190a3 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 0fe2fe0011..1cb4f5598d 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 130/421] 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 360ade6215..11ff71a6a6 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 e3aa9190a3..f528fa3bee 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 131/421] 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 2ea1a02869..a9458833ba 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 71b80de76f..fab3aee350 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 132/421] 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 f528fa3bee..18ada811c5 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 133/421] 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 b164ae060d..0f59b509b3 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 134/421] 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 18ada811c5..7a80af9e46 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 135/421] 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 32c2b59e08..7b80e335f2 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 ee2c8ab250..eeaad5eea7 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 136/421] 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 015c0dbb55..4186309398 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 137/421] 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 9cc23fc2d6..771b577ff1 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 138/421] 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 25e8ff60ed..9a31799273 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 7a6738a684..007d9b7551 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 9ff7c03857..15d9930d3d 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 139/421] 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 d707b4bfe7..d8cd5d1acc 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 140/421] 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 771b577ff1..7ff599979d 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 8cf0932a58..d02b829b97 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 21a0a48012..6cc2a47af4 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 141/421] 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 7ff599979d..5ce9d8efc1 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 142/421] 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 5ce9d8efc1..fc1d01cf3f 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 143/421] 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 592c2abc87..3ac51ad357 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 2a560371ca..2675d46ba2 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 ce077bc691..b53bf92bd9 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 144/421] 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 b81ad195ce..f0913ea5e4 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 145/421] 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 a9458833ba..95b1807d72 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 b53bf92bd9..1a5a3c5ff5 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 146/421] support go 1.15 (#2463) --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6680a5b360..d7086b3832 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 147/421] 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 95b1807d72..216cecae34 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 1a5a3c5ff5..e2f8de06e4 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 148/421] Fix typo (#2489) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc1d01cf3f..a40d637351 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 149/421] 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 a40d637351..0515e99d5e 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 150/421] 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 216cecae34..5b60d8b071 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 e2f8de06e4..8e1e3b57ff 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 151/421] 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 5b60d8b071..71fb5937dd 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 152/421] 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 fab3aee350..c32f0eeb0e 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 9b57c57b0a..cc486c35ed 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 153/421] 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 d02b829b97..563f5aaa8e 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 154/421] 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 0515e99d5e..7e8359eec6 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 155/421] 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 30f5f53241..461b720af5 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 156/421] 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 9a31799273..0f276c13d4 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 0000000000..a8f9a94e10 --- /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 157/421] 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 cfaee74622..884ff85172 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 4c14fb83f7..a64b331924 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 158/421] 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 d7086b3832..0795665dc6 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 7e8359eec6..18b19430f1 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 159/421] 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 4424bab9cd..c354be945d 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 f0913ea5e4..2f4e45b40f 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 cae4ccccdd..fbd5c52743 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 160/421] 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 71fb5937dd..3d6b56d6b3 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 161/421] 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 7b80e335f2..fdad2015c4 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 162/421] 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 7a80af9e46..74e07e84f3 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 163/421] 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 57562845fa..5c8e235b5f 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 c354be945d..1733617724 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 a4c1a7f6ce..c57a120fc2 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 0000000000..e9c6de447d --- /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 164/421] 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 0795665dc6..91129313f1 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 165/421] 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 91129313f1..0795665dc6 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 166/421] 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 5f508c7090..41ad98743b 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 167/421] 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 11f833e9a3..c8813aff26 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 168/421] 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 c66ca4408f..4c7cd0c39e 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 d8cd5d1acc..c2272d0f86 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 169/421] 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 18b19430f1..0c2632441b 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 170/421] 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 43ad36f53b..4d8a6ce484 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 171/421] 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 0795665dc6..8ebae71234 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 dda19bcf3e..a477611bff 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 0f59b509b3..c11ee99ae7 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 3ac51ad357..ddf30e189b 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 98d758ef1c..97daa808f0 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 0c2632441b..119f9452a3 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 172/421] 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 fdad2015c4..86e4c4d44c 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 173/421] 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 5c8e235b5f..5caeb581af 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 9791a60763..04d9407971 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 fd227b11a8..9afa3dcf6d 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 a5bc2ad297..2a442996a6 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 296d3eb18c..75600ba830 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 38c189a0b8..d565843489 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 a8f9a94e10..5fb6057baa 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 480e8bff48..172aeb2414 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 649a3cdb6a..232f8dcada 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 be2d45c51c..6ef5b6e514 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 e439ac48ed..8170fbe815 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 174/421] 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 11ff71a6a6..485f0eea84 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 175/421] 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 a477611bff..c634e6be05 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 ddf30e189b..a56d4ff8a1 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 119f9452a3..eb84031a39 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 74e07e84f3..ca753e6d27 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 1cb4f5598d..d7c4fb0b9c 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 176/421] 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 eb84031a39..d4772d764b 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 3d6b56d6b3..1ba0fa2b0d 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 8e1e3b57ff..8fe4761570 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 1e12617904..03a0e127e6 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 41ad98743b..fd972657cc 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 11bdd79ccf..678d95f2c8 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 177/421] 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 a56d4ff8a1..44d62514a9 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 3e9687dc7b..95b4ed14c9 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 178/421] 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 1ba0fa2b0d..dc03c358a3 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 8fe4761570..cf3f0be964 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 0d40666ec3..80961ce173 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 179/421] 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 44d62514a9..dc2c2f5546 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 95b4ed14c9..3647461b42 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 180/421] 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 8ebae71234..bcc21414d1 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 d4772d764b..e5e3b46ac1 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 4c7cd0c39e..9bacc68571 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 c2272d0f86..0550999288 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 5fb6057baa..0000000000 --- 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 6aae1c10b3..ee95ab31ec 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 181/421] 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 bcc21414d1..816623158b 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 cf3f0be964..8e9f7264be 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 485f0eea84..036fa1c349 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 182/421] 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 8e9f7264be..993c632f74 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 183/421] 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 15d9930d3d..6f14bf5935 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 184/421] 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 e5e3b46ac1..2e953f5aae 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 a64b331924..ac92ada39c 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 0000000000..da960571a8 --- /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 172aeb2414..75b6022409 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 185/421] 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 d62e070594..45aaa49487 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 186/421] 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 2e953f5aae..566e5cc709 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 187/421] 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 566e5cc709..6f9178cac1 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 188/421] 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 d361b74d32..22138a8d30 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 189/421] 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 ca753e6d27..0d082d0571 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 d7c4fb0b9c..298c5ed0c5 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 190/421] 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 6f9178cac1..38c674870f 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 191/421] 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 dc2c2f5546..a28edc840a 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 3647461b42..a80ab69a8f 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 192/421] 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 dc03c358a3..12e946d211 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 193/421] 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 884ff85172..d18da17911 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 ac92ada39c..e30155cd9d 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 194/421] 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 4c75d1feb8..4aaa60be98 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 195/421] 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 d18da17911..9484b26442 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 e30155cd9d..e61ef908a0 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 196/421] Fixed typo in documentation (#2733) --- fs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs.go b/fs.go index 007d9b7551..e5f3d602a3 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 197/421] 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 993c632f74..aaa358e9d0 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 03a0e127e6..56e5c76848 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 fd972657cc..2eb2d52bf8 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 198/421] 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 c57a120fc2..ee69329ecc 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 199/421] 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 12e946d211..0c1fb07f48 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 aaa358e9d0..e0de717ea7 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 56e5c76848..00686e77e1 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 200/421] 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 2f4e45b40f..421c0f7124 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 179ce4ea20..b99302af82 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 f85a1aa604..69c0a5443d 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 4186309398..e25415b001 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 201/421] 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 b93c34cf42..040af9e209 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 9bacc68571..ed313868e9 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 202/421] 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 0d082d0571..051c74aae6 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 203/421] 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 051c74aae6..5f35fff473 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 204/421] 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 e86bc98f82..96e70bba44 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 0000000000..85cc560ed7 --- /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 816623158b..0000000000 --- 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 97daa808f0..d1c723c67b 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 1a99193959..5d55b444ce 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 38c674870f..ef37fd200d 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 205/421] 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 0c1fb07f48..5c1aba5e61 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 d565843489..8bf938961d 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 e0de717ea7..93b58ee8e1 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 00686e77e1..fd4fa51241 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 206/421] 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 563f5aaa8e..8519959141 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 207/421] 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 8519959141..3101fe28a0 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 208/421] 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 5c1aba5e61..ede16fd4d8 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 93b58ee8e1..2a4d21856e 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 209/421] 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 6ef5b6e514..7f17ca4d9c 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 8170fbe815..d16cf6e6e2 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 210/421] 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 5f35fff473..4b5656fb08 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 298c5ed0c5..7459317fa3 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 211/421] 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 ee69329ecc..87fc4c665b 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 0000000000..839cf710b5 --- /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 e9c6de447d..e9debe59bf 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 212/421] 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 ef37fd200d..ef80117935 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 421c0f7124..cb66dd4a70 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 2675d46ba2..608594e28d 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 213/421] Fix typo (#2772) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index ede16fd4d8..8aabf7ae0f 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 214/421] byte alignment (#2774) --- gin.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/gin.go b/gin.go index fd4fa51241..7bdb68574b 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 215/421] 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 5caeb581af..deb71661b8 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 9afa3dcf6d..2342447070 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 216/421] 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 8aabf7ae0f..ecf74ba9b1 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 217/421] 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 7bdb68574b..6ab2be66d0 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 218/421] use assert1 func (#2783) --- routergroup.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/routergroup.go b/routergroup.go index 6f14bf5935..bb24bd5232 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 219/421] 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 0e49d65b14..d6d8b45269 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 220/421] 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 2eb2d52bf8..ed7196fd6e 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 4b5656fb08..2e46b8e513 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 7459317fa3..91213eee06 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 221/421] 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 ed7196fd6e..094c46e871 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 2e46b8e513..eb549591d6 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 91213eee06..ea13c30ee9 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 222/421] 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 6cc2a47af4..d164bfa369 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 223/421] 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 ecf74ba9b1..1a953006c0 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 2a4d21856e..ffbe5ccaa3 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 224/421] 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 a28edc840a..308af74c36 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 a80ab69a8f..535bfc8273 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 225/421] 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 ef80117935..198bf01193 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 226/421] 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 0000000000..632e8eb25c --- /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 85cc560ed7..ab450ab834 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 0000000000..1c3cb758e0 --- /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 1733617724..17df7dc59d 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 cb66dd4a70..54b3eabd6c 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 608594e28d..516554eb7d 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 f9ece928d4..ca02897a9f 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 f91ec38199..a3c0df515c 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 ffbe5ccaa3..176eaae6b6 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 9484b26442..bf68259328 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 e61ef908a0..351ee253da 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 c8813aff26..4d199df306 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 e25415b001..b80adcff34 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 15aca9959c..1d2aa871c7 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 353c82bbf2..22e7d5ab01 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 1f113e74d6..9061d02101 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 21997ca1c6..bf45e02891 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 ea13c30ee9..cbb37340ef 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 227/421] Update .golangci.yml (#2829) --- .golangci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 1c3cb758e0..cfcff00cc0 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 228/421] Delete unused static const (#2830) --- tree.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tree.go b/tree.go index eb549591d6..fb0a5935c2 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 229/421] 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 54b3eabd6c..f8b4b12393 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 230/421] 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 ab450ab834..21895f2899 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 231/421] Delete deadcode exclude rules (#2838) --- .golangci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index cfcff00cc0..78a4259a18 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 232/421] 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 1a953006c0..56c20f2c61 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 233/421] 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 b80adcff34..3ebcee9706 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 234/421] 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 bf68259328..844dc453cd 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 351ee253da..266f4ac5cb 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 235/421] 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 0000000000..053e6c5aab --- /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 0000000000..431d54c7bf --- /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 176eaae6b6..7c5626e1be 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 236/421] 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 21895f2899..0ebe2ec560 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 237/421] 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 844dc453cd..26b927101d 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 266f4ac5cb..8aaf41a352 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 238/421] 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 0ebe2ec560..6eb5a64d1f 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 239/421] 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 56c20f2c61..62849488b2 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 7c5626e1be..f9792e326f 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 240/421] 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 17df7dc59d..5b0ce39d3e 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 ca02897a9f..a4e471535c 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 241/421] 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 cbb37340ef..8ae5b7db65 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 242/421] 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 26b927101d..92cca49f79 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 8aaf41a352..969d49c0e5 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 243/421] 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 6eb5a64d1f..15c2530aa1 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 78a4259a18..ba5f4d1e50 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 040af9e209..fa2a6540a0 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 62849488b2..8a2f46d1cb 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 f9792e326f..ad16fbb1c6 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 3101fe28a0..39f1355138 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 22e7d5ab01..e417731a89 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 244/421] 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 92cca49f79..80807e85be 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 969d49c0e5..33ce8cd709 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 245/421] 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 198bf01193..10be5e155d 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 246/421] 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 6ab2be66d0..ae6d479917 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 247/421] Add gosec (#2882) --- .golangci.yml | 62 +++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ba5f4d1e50..c5e1de388a 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 248/421] 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 8a2f46d1cb..370f608634 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 249/421] 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 ae6d479917..02a1062c31 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 fca1c530b3..4b4afd4af0 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 250/421] =?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 f8b4b12393..fa7ad1bc4e 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 69c0a5443d..c4d7ed7421 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 ee95ab31ec..9a17d8594e 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 251/421] 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 10be5e155d..62094ff101 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 370f608634..d211098b72 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 ad16fbb1c6..07a35f523b 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 02a1062c31..af83161b68 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 094c46e871..0b67b542de 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 678d95f2c8..21c43d1520 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 252/421] 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 80807e85be..c03439a018 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 33ce8cd709..7bf71f9ffe 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 253/421] 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 d211098b72..bea95ccaba 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 07a35f523b..e9fe88f920 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 254/421] 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 c03439a018..c25eecf9df 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 7bf71f9ffe..51497a0bac 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 255/421] 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 62094ff101..0fa78ecaba 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 256/421] 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 bea95ccaba..bc2c38e1d8 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 af83161b68..1774717f83 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 0b67b542de..8c22e7bdad 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 fb0a5935c2..c8a7548e4f 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 8ae5b7db65..49b3b57ef6 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 257/421] 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 0fa78ecaba..cad746d62c 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 bc2c38e1d8..58f38c88cb 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 e9fe88f920..c286c0f4cb 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 1774717f83..1d9c9fee44 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 258/421] 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 bb24bd5232..27d7aad671 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 259/421] 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 036fa1c349..ffe3469e7c 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 c8a7548e4f..a0d9835209 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 260/421] Fix typo (#2926) --- errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/errors.go b/errors.go index 0f276c13d4..3418cbc806 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 261/421] 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 a0d9835209..2f0de1a96e 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 49b3b57ef6..c372339673 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 262/421] Update version.go (#2923) --- version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.go b/version.go index 535bfc8273..b9110adbc4 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 263/421] 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 15c2530aa1..c25a909178 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 264/421] Fix grammar (#2933) --- context.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 58f38c88cb..2aa91e11e0 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 265/421] 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 87fc4c665b..bd8764b194 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 839cf710b5..8d628369ea 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 e9debe59bf..ff1301020e 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 266/421] 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 308af74c36..4c806a5a7d 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 c25eecf9df..8a5d7205aa 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 b9110adbc4..4b69b9b91c 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 267/421] fix: typo (#2958) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 1d9c9fee44..21513c6826 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 268/421] fix: description error (#2961) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 2aa91e11e0..a97dd4096a 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 269/421] Update README.md (#2954) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cad746d62c..e3d6ab2f84 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 270/421] 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 2f0de1a96e..521d3ef382 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 271/421] 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 a97dd4096a..30a869823c 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 21513c6826..d507b51d28 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 272/421] 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 d507b51d28..94de631ccb 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 22138a8d30..61b84546d8 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 39f1355138..40eba3b202 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 273/421] 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 30a869823c..4fb3ae6dc4 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 c286c0f4cb..4d002c2375 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 94de631ccb..45f4f2cf0f 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 274/421] 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 45f4f2cf0f..dac274c2ba 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 3080fd34fb..ed054bfd25 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 275/421] 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 dac274c2ba..54e1a00c3a 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 276/421] 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 8a5d7205aa..05b9760f63 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 51497a0bac..adacf5ee6d 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 277/421] Update README.md (#2985) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e3d6ab2f84..f78a64ebcb 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 278/421] 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 521d3ef382..a30d349606 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 c372339673..94c533864e 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 279/421] 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 c634e6be05..533204edf2 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 54e1a00c3a..9bd4c46af6 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 4d199df306..1fb994b42c 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 280/421] 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 4fb3ae6dc4..231bb14277 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 281/421] 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 deb71661b8..0414a3453b 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 2342447070..f0b667b215 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 282/421] Update README.md (#2997) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f78a64ebcb..6ae567b314 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 283/421] 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 6ae567b314..b75df63fce 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 284/421] fix: typo (#3006) --- context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context.go b/context.go index 231bb14277..cccb013a82 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 285/421] 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 4d002c2375..9e02aede13 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 21c43d1520..0aece61d16 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 3f440bceb8..e74bddd55c 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 80961ce173..ec5e6cdccd 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 4b4afd4af0..e0a756c3de 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 d164bfa369..ac3669a419 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 d6d8b45269..232f595b0a 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 ffe3469e7c..b3f0c47b88 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 cc486c35ed..b50914f25e 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 286/421] 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 05b9760f63..118eb762c1 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 adacf5ee6d..c16d8c81a9 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 287/421] 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 118eb762c1..35488359d0 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 c16d8c81a9..59efbf75d0 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 288/421] 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 ed054bfd25..0802e08567 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 289/421] 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 cccb013a82..d69df70b24 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 290/421] fix typo (#3044) --- tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index a30d349606..18e50ee363 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 291/421] 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 c25a909178..f3927b3d46 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 292/421] 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 0000000000..4a081e0cd1 --- /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 35488359d0..b088f26bb9 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 293/421] 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 9bd4c46af6..b54dbd8a62 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 0aece61d16..629a109b2d 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 b088f26bb9..b59e590728 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 59efbf75d0..c01ba52431 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 294/421] 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 629a109b2d..ae1762efd7 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 295/421] Update routergroup.go (#3056) --- routergroup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 27d7aad671..b84fcca3d8 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 296/421] 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 b59e590728..8087227e67 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 c01ba52431..7f892453b7 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 297/421] 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 b75df63fce..d3fa075764 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 298/421] 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 4a081e0cd1..4cbc4554fb 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 f3927b3d46..5b818a9e24 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 299/421] Distinguish between group and nested group (#3083) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d3fa075764..2d113532bb 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 300/421] 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 d69df70b24..48d7550697 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 9e02aede13..4eed164ab1 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 c32f0eeb0e..e4599ea927 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 b50914f25e..d2a740bfb1 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 301/421] 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 2d113532bb..4b4d236bbe 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 b84fcca3d8..3fba3a9107 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 232f595b0a..c1fad3a984 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 b3f0c47b88..4a0cb49378 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 302/421] 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 4a0cb49378..5643097069 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 303/421] fix: typo (#3086) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b4d236bbe..9a9785e642 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 304/421] 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 5b818a9e24..1aecda580b 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 305/421] 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 18e50ee363..88100eec84 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 306/421] 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 8087227e67..7a9249c6e0 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 7f892453b7..6006ffb989 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 307/421] 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 0000000000..a0104dc899 --- /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 0000000000..1331d390dd --- /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 0414a3453b..703a1cf87e 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 f0b667b215..b3818549f3 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 5b0ce39d3e..b1edbf5a03 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 bd8764b194..3515a8cc57 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 ff1301020e..df7742b755 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 fa2a6540a0..f5cbf57b2e 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 fa7ad1bc4e..c24dd553df 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 516554eb7d..78f4df0e44 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 b99302af82..14f525ef5f 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 45aaa49487..2e3e1dd5c3 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 2a442996a6..65197713e6 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 75600ba830..11561c843d 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 4aaa60be98..545914d054 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 a4e471535c..ace8f6547d 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 219743f2a9..9790ce6a26 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 a3c0df515c..4d44ab98b8 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 5299fbf602..d05c891ea1 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 4e90114962..e62af4a679 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 a2d36d6a54..183f141ed6 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 48d7550697..faa481332a 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 4eed164ab1..fb46e6792f 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 ed313868e9..badcb5e1a3 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 ab4474296e..004e13dbb6 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 3418cbc806..578e857d6a 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 9a17d8594e..ac72dc42c9 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 b54dbd8a62..b0e0154fac 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 ae1762efd7..0c11134f04 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 61b84546d8..ab43d1123b 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 ec5e6cdccd..da1b654ec1 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 40eba3b202..3efe146b14 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 ac3669a419..2c327a6586 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 0000000000..57349be316 --- /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 6696ece997..bdfaf11ace 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 3ebcee9706..0a7dcef290 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 7f17ca4d9c..2c0e9aa8d0 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 1d2aa871c7..2e57f5ca0f 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 d16cf6e6e2..7b3601a0fb 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 e417731a89..8b28dc3f0e 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 461b720af5..b77a776b0f 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 cc5390a2d0..c396a5a12e 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 0df7836089..0fc7a665d8 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 0000000000..f16864c55e --- /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 bf45e02891..6687aae2be 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 94c533864e..085b58037b 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 e4599ea927..b41a3b0f6a 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 308/421] 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 1aecda580b..fa9d68b34b 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 9a9785e642..4aa638d62b 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 badcb5e1a3..d8d6c8e12c 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 0550999288..7c54444ae0 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 309/421] 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 fa9d68b34b..c432a33758 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 7a9249c6e0..dcd83686d7 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 6006ffb989..c0980ad527 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 310/421] 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 b0e0154fac..c2e95f29b9 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 ab43d1123b..1f9d63ae7b 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 26826689a5..7f9095f015 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 311/421] 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 c432a33758..f67641a918 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 312/421] 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 dcd83686d7..846bd5df0a 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 c0980ad527..c92ce01ab0 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 313/421] 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 846bd5df0a..6c46755d40 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 c92ce01ab0..5108a5998e 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 314/421] 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 6c46755d40..bfb462b8ed 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 5108a5998e..983b50a4d6 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 315/421] docs: Update README.md (#3108) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4aa638d62b..af9a1e5379 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 316/421] 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 faa481332a..0fe1333170 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 317/421] 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 f67641a918..771c0b9bce 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 318/421] 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 771c0b9bce..c5e2744dd7 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 319/421] 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 8c22e7bdad..0dfa903277 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 0c11134f04..a438062279 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 da1b654ec1..b704998891 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 8bc757030d..f3f530a451 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 320/421] 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 b1edbf5a03..d9746e266a 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 e74bddd55c..5fe65a4b1c 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 321/421] use StringToBytes func (#2798) --- auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth.go b/auth.go index 4d8a6ce484..482c499af5 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 322/421] 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 0fe1333170..5e53aaf0f2 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 fb46e6792f..20038e935f 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 323/421] 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 bfb462b8ed..2da1fe35c4 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 983b50a4d6..d7a221f1c7 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 324/421] 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 4cbc4554fb..e27022d109 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 325/421] 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 1fb994b42c..4e7d734a72 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 1b5fb2ff03..6fd9a13721 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 326/421] 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 c5e2744dd7..e8ef30ca73 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 327/421] 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 c2e95f29b9..3a831e5b33 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 328/421] 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 0000000000..eca089ce09 --- /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 329/421] [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 af9a1e5379..6b4cabb41b 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 703a1cf87e..50510514cd 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 b3818549f3..7f6a904ab0 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 d9746e266a..f08e173f0a 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 0000000000..5b9ad165d1 --- /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 0000000000..2bc0e3a47e --- /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 5e53aaf0f2..ecbd3dac7d 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 20038e935f..7e4d0b3fde 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 2da1fe35c4..e519b36ec2 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 d7a221f1c7..d7555f9a6c 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 bcd568bfba..1fa48061fa 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 0000000000..1192c78cbd --- /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 330/421] 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 a0104dc899..42b1ea46f7 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 482c499af5..2503c5156a 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 e44bd100ac..42b6f8fddf 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 0b3f82df7d..5b7929b854 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 1331d390dd..d8251a7c20 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 50510514cd..a58924ed3e 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 f08e173f0a..f09962164d 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 3515a8cc57..c03afe75be 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 8d628369ea..9292e2aaf1 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 f5cbf57b2e..b17352bad0 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 c24dd553df..98cebfecf7 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 9572ea03dc..5788133f2d 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 14f525ef5f..03bc78dae1 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 2e3e1dd5c3..36eb27a344 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 65197713e6..d1f035e44a 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 c4d7ed7421..4ebe83263c 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 545914d054..9932860386 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 ace8f6547d..44f2fdb93d 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 9790ce6a26..c958b88bda 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 5b9ad165d1..83b27267b1 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 d05c891ea1..801bd9b7c5 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 e62af4a679..a70f4ad36c 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 183f141ed6..b0d36a3589 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 ecbd3dac7d..6b25b3a31d 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 053e6c5aab..267605072e 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 431d54c7bf..69c97864ce 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 8bf938961d..931313f613 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 eca089ce09..0000000000 --- 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 7e4d0b3fde..f9f0c1dc19 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 d8d6c8e12c..25fd7c87f9 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 7c54444ae0..4ac55fe148 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 004e13dbb6..fdad855477 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 f8df651c34..0240b2ec75 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 578e857d6a..2853ce8ed8 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 ac72dc42c9..78d561c63b 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 e5f3d602a3..64274735e2 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 3a831e5b33..e516360caa 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 0802e08567..1550b868d1 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 0dfa903277..b0532a25d1 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 a438062279..02f23247d5 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 5fe65a4b1c..c6350e813e 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 e519b36ec2..6cdde34e63 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 d7555f9a6c..d06b810376 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 da960571a8..23f717265e 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 75b6022409..a26d7db2e2 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 232f8dcada..853b1a901e 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 1f9d63ae7b..cd1e7fa6e9 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 b704998891..fa0d9ce8be 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 e0a756c3de..a235fe9192 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 4e7d734a72..545fdaaf8f 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 6fd9a13721..2407f46339 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 3efe146b14..abb645105a 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 2c327a6586..347917e7b5 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 57349be316..b19ad45d9f 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 6ba657ba0a..a653ea3091 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 bdfaf11ace..c308408d2a 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 0a7dcef290..af678e80d1 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 2c0e9aa8d0..e0f30f7a97 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 2e57f5ca0f..9331c40583 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 d5282e4927..5752d8d859 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 3930f51df1..aaceb9eaac 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 c006691ca6..70e3a47e81 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 1fa48061fa..7955000c6e 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 8b28dc3f0e..a13fff4242 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 b77a776b0f..77eafdfd84 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 1192c78cbd..40f044c88a 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 c396a5a12e..6af8901796 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 0fc7a665d8..4f0ac01f62 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 7f9095f015..77c7ed8fda 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 9061d02101..57d163c9f1 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 3fba3a9107..3c082d932b 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 c1fad3a984..41f963724e 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 5643097069..d7034b2292 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 3a7a5ddf69..b3be93b4ee 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 f16864c55e..2203f33a32 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 b41a3b0f6a..4021a2ab40 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 d2a740bfb1..058ddb9d70 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 4b69b9b91c..40e6505b31 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 331/421] 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 0000000000..e0f6e4f6fb --- /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 0000000000..e435e56aa5 --- /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 40e6505b31..9b0eae22a7 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 332/421] 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 533204edf2..b4773ef3de 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 4c806a5a7d..6d540289d1 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 83b27267b1..a6b8a90abd 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 4d44ab98b8..29151064a9 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 7b3601a0fb..642123614e 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 333/421] 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 e0f6e4f6fb..64ed8b2b6e 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 334/421] 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 6d540289d1..1e7e83ee1f 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 335/421] fix: typo (#3171) --- README.md | 75 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 6b4cabb41b..5cc8321b63 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 336/421] 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 6b25b3a31d..b1ad95e623 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 f9f0c1dc19..6c0be544ef 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 e516360caa..f9324299b7 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 337/421] 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 1e7e83ee1f..1bc51a8c9b 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 9b0eae22a7..632ca7d1d6 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 338/421] 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 6cdde34e63..2d2c7dcb63 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 d06b810376..18233de494 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 339/421] 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 b1ad95e623..46bf113348 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 340/421] 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 2d2c7dcb63..901034637c 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 18233de494..620eccec5c 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 341/421] 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 88100eec84..956bf4dd3c 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 342/421] 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 901034637c..f759066175 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 620eccec5c..a590d38fb8 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 343/421] 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 e8ef30ca73..6dc787a2d0 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 5cc8321b63..2477d0b04d 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 25fd7c87f9..8367de9c10 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 4ac55fe148..5c29a74bb0 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 344/421] 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 f759066175..283c6a5019 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 a590d38fb8..4d6c06eabc 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 345/421] 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 8367de9c10..b9f8234af3 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 5c29a74bb0..bf0e6ab847 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 346/421] 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 6c0be544ef..d09b0ae1ea 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 347/421] 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 283c6a5019..6371d226f8 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 4d6c06eabc..aba0019912 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 348/421] 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 6371d226f8..ae7a5f8177 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 aba0019912..3529342cad 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 349/421] 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 ae7a5f8177..0b259e132c 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 3529342cad..640ed10e4d 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 350/421] 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 2477d0b04d..1c315f8869 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 351/421] 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 6dc787a2d0..cb3a49a831 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 5d55b444ce..ebde4ee840 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 1c315f8869..8d7b5ec428 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 0b259e132c..ed6402e30b 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 640ed10e4d..9b4fb91e46 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 a26d7db2e2..c5f3efc88d 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 0000000000..5a9ca4b2d0 --- /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 352/421] 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 ed6402e30b..40f874ea3b 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 9b4fb91e46..30dc0ce4f9 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 353/421] 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 40f874ea3b..924cefc364 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 30dc0ce4f9..58be7b010c 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 354/421] 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 cb3a49a831..cff8f4a6dc 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 46bf113348..f9489a7734 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 d09b0ae1ea..b3e81c1482 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 2853ce8ed8..ca2bfc3fdc 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 78d561c63b..f77a634296 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 1550b868d1..ea38c613ce 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 fa0d9ce8be..7bc1137109 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 a235fe9192..acdf89c42e 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 545fdaaf8f..fd26d907cc 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 d42d6b9d05..82438c1378 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 abb645105a..05b30d954d 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 3c082d932b..2474a81c15 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 355/421] 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 cff8f4a6dc..14de5f7288 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 356/421] 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 924cefc364..54cc55230c 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 58be7b010c..b88e4a820f 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 357/421] 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 14de5f7288..004f9b9df0 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 358/421] 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 2474a81c15..8b877edacb 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 359/421] 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 54cc55230c..885ff0ee41 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 b88e4a820f..8b560189a9 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 360/421] 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 885ff0ee41..daf52e2a42 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 8b560189a9..4d83273133 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 361/421] 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 69c97864ce..23377ffdd3 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 02f23247d5..4fac677ac6 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 362/421] 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 8b877edacb..27308bc8fd 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 363/421] 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 daf52e2a42..fa1db99ac4 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 4d83273133..1c31d29957 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 364/421] 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 64ed8b2b6e..654585b91d 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 365/421] 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 004f9b9df0..686d08a3ac 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 fa1db99ac4..1c64cf1b15 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 1c31d29957..35bfd2e9a8 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 366/421] 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 8d7b5ec428..960c66dc1e 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 b9f8234af3..cbcedbc98f 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 bf0e6ab847..abe8b41cad 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 367/421] =?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 686d08a3ac..18b36d0eb1 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 23377ffdd3..0f8527fe09 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 0000000000..4b34ea2472 --- /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 368/421] 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 98cebfecf7..540bbbb84a 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 369/421] 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 f9489a7734..b66b8adc15 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 370/421] 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 f09962164d..eae278022f 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 78f4df0e44..93d6a92ff8 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 a13fff4242..3509db3cec 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 371/421] 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 c03afe75be..e216b8546c 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 b3e81c1482..cc55cb012d 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 ca2bfc3fdc..06b53c28b3 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 f9324299b7..a2e2e67c13 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 77c7ed8fda..43e828d712 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 27308bc8fd..dfbdd7b8d3 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 372/421] 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 956bf4dd3c..3f34b8ee82 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 085b58037b..2005738ec6 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 373/421] 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 1bc51a8c9b..a682c8c682 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 960c66dc1e..760bde0284 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 374/421] 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 cc55cb012d..85e0a6161e 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 a2e2e67c13..35159d03fa 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 b3be93b4ee..7508c5c90a 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 375/421] 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 760bde0284..8a6b262a59 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 376/421] 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 8a6b262a59..6445e97d12 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 377/421] 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 4fac677ac6..5ab2430ce6 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 378/421] 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 1c64cf1b15..ebc1bd1a53 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 35bfd2e9a8..acdf805dd6 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 379/421] 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 18b36d0eb1..45ae932e21 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 380/421] 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 b66b8adc15..ac9db17e1c 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 381/421] 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 6445e97d12..48a0a13cdb 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 382/421] 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 48a0a13cdb..65492e52d0 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 383/421] 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 eae278022f..9af4f88aa3 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 9932860386..4e97c0f0a6 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 44f2fdb93d..57721fc9f9 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 ac9db17e1c..c41c71ecf9 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 b0532a25d1..02b9622119 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 5ab2430ce6..8825ac7ef8 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 05b30d954d..3a90f2505c 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 d7034b2292..cd8cf14145 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 384/421] 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 65492e52d0..103a53c74a 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 385/421] 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 45ae932e21..7a4e61c617 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 386/421] 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 ebc1bd1a53..200a4403f7 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 acdf805dd6..574e4a9ae7 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 387/421] 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 c41c71ecf9..737e4d7a37 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 388/421] 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 654585b91d..3af3a455e1 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 389/421] 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 200a4403f7..1929c9591d 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 574e4a9ae7..054058aa27 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 390/421] 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 1929c9591d..7a355e981d 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 054058aa27..516c053c65 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 391/421] 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 a682c8c682..2ab5617924 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 632ca7d1d6..37e27f27a5 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 392/421] 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 3a90f2505c..2955c03a0e 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 393/421] 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 3509db3cec..c5c5375f53 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 394/421] 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 7a355e981d..e969833934 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 516c053c65..6d8df64a0f 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 395/421] 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 103a53c74a..eccf814c2e 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 0000000000..008a91db78 --- /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 396/421] 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 43e828d712..5cec1f6616 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 57d163c9f1..6fa5ec719c 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 397/421] 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 737e4d7a37..b1352b9b6e 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 398/421] 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 b0d36a3589..2535f8c33d 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 e969833934..2b7a98cc94 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 6d8df64a0f..a4f0f387ac 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 c5c5375f53..ebb7d41472 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 4f0ac01f62..fc927c1f28 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 399/421] 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 2b7a98cc94..daff61de97 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 a4f0f387ac..c7e0b38a7c 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 400/421] 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 daff61de97..8cb21f9c80 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 c7e0b38a7c..daf51f7d7c 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 401/421] 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 008a91db78..7cebab566e 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 402/421] 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 cd8cf14145..ada8e1e457 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 3f34b8ee82..dda8f4f7b0 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 403/421] 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 8cb21f9c80..0b1d3a6ba4 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 daf51f7d7c..f86942b5d1 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 404/421] 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 0b1d3a6ba4..c498b482ec 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 f86942b5d1..409d393b3a 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 405/421] 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 b1352b9b6e..0474252709 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 85e0a6161e..827ee0fafd 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 406/421] 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 c498b482ec..e03001ec7e 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 409d393b3a..4e4e963242 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 407/421] 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 a6b8a90abd..a66b93aafe 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 408/421] 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 eccf814c2e..336155a990 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 409/421] 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 abe8b41cad..ce8b19da7b 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 c6350e813e..9276bed508 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 7bc1137109..5f78708f1a 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 347917e7b5..fa8ab894be 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 410/421] 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 7a4e61c617..9bd2698c6d 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 411/421] 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 e03001ec7e..ea2c94acd5 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 4e4e963242..09341d4505 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 412/421] 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 dfbdd7b8d3..c833fe8fe3 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 41f963724e..6848063e29 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 413/421] 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 ea2c94acd5..4ec5325779 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 09341d4505..b2d2f73688 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 414/421] 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 0474252709..556f8ac9cc 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 827ee0fafd..1ab6b33949 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 af678e80d1..fc8dea453f 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 ebb7d41472..192552513f 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 415/421] =?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 9bd2698c6d..fac97d4787 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 4ec5325779..51353f5448 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 b2d2f73688..01f9495294 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 416/421] 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 5cec1f6616..753a0b09ab 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 6fa5ec719c..9fd5e87cce 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 417/421] 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 51353f5448..2ef45cfee6 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 01f9495294..b47804fcc0 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 418/421] 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 35159d03fa..32dae2497f 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 ada8e1e457..5310caec25 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 419/421] 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 2ef45cfee6..f7e28d8cae 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 b47804fcc0..814f4eb377 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 420/421] 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 32dae2497f..f95e5dda54 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 5310caec25..633c0aba14 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 421/421] 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 2ab5617924..cf24ec2867 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 f7e28d8cae..db36337e32 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 814f4eb377..8bdb934a1f 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 37e27f27a5..390da4f3e4 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"