From f76ccb25f1eee8684e545ec00f8f2934fec98f09 Mon Sep 17 00:00:00 2001 From: Sai Date: Wed, 12 Dec 2018 10:05:16 +0900 Subject: [PATCH 01/23] Add LoggerWithFormatter method (#1677) * Add LoggerWithFormatter * Add tests for LoggerWithFormatter & LoggerWithConfig * Add note for README * Add tests for DefaultLogFormatter * Add comment * Change DefaultLogFormatter to a private method --- README.md | 38 ++++++++++ logger.go | 116 +++++++++++++++++++++++------ logger_test.go | 194 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 324 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index e7b92b2d..c1f902a9 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) - [Using middleware](#using-middleware) - [How to write log file](#how-to-write-log-file) + - [Custom Log Format](#custom-log-format) - [Model binding and validation](#model-binding-and-validation) - [Custom Validators](#custom-validators) - [Only Bind Query String](#only-bind-query-string) @@ -528,6 +529,43 @@ func main() { } ``` +### Custom Log Format +```go +func main() { + router := gin.New() + + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + + // your custom format + return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", + param.ClientIP, + param.TimeStamp.Format(time.RFC1123), + param.Method, + param.Path, + param.Request.Proto, + param.StatusCode, + param.Latency, + param.Request.UserAgent(), + param.ErrorMessage, + ) + })) + router.Use(gin.Recovery()) + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +**Sample Output** +``` +::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " +``` + ### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). diff --git a/logger.go b/logger.go index 74dd2e6f..a64af697 100644 --- a/logger.go +++ b/logger.go @@ -26,6 +26,56 @@ var ( disableColor = false ) +// LoggerConfig defines the config for Logger middleware. +type LoggerConfig struct { + // Optional. Default value is gin.defaultLogFormatter + Formatter LogFormatter + + // Output is a writer where logs are written. + // Optional. Default value is gin.DefaultWriter. + Output io.Writer + + // SkipPathes is a url path array which logs are not written. + // Optional. + SkipPathes []string +} + +// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter +type LogFormatter func(params LogFormatterParams) string + +// LogFormatterParams is the structure any formatter will be handed when time to log comes +type LogFormatterParams struct { + Request *http.Request + TimeStamp time.Time + StatusCode int + Latency time.Duration + ClientIP string + Method string + Path string + ErrorMessage string + IsTerm bool +} + +// defaultLogFormatter is the default log format function Logger middleware uses. +var defaultLogFormatter = func(param LogFormatterParams) string { + var statusColor, methodColor, resetColor string + if param.IsTerm { + statusColor = colorForStatus(param.StatusCode) + methodColor = colorForMethod(param.Method) + resetColor = reset + } + + return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", + param.TimeStamp.Format("2006/01/02 - 15:04:05"), + statusColor, param.StatusCode, resetColor, + param.Latency, + param.ClientIP, + methodColor, param.Method, resetColor, + param.Path, + param.ErrorMessage, + ) +} + // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { disableColor = true @@ -50,12 +100,39 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc { // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. // By default gin.DefaultWriter = os.Stdout. func Logger() HandlerFunc { - return LoggerWithWriter(DefaultWriter) + return LoggerWithConfig(LoggerConfig{}) +} + +// LoggerWithFormatter instance a Logger middleware with the specified log format function. +func LoggerWithFormatter(f LogFormatter) HandlerFunc { + return LoggerWithConfig(LoggerConfig{ + Formatter: f, + }) } // LoggerWithWriter instance a Logger middleware with the specified writer buffer. // Example: os.Stdout, a file opened in write mode, a socket... func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { + return LoggerWithConfig(LoggerConfig{ + Output: out, + SkipPathes: notlogged, + }) +} + +// LoggerWithConfig instance a Logger middleware with config. +func LoggerWithConfig(conf LoggerConfig) HandlerFunc { + formatter := conf.Formatter + if formatter == nil { + formatter = defaultLogFormatter + } + + out := conf.Output + if out == nil { + out = DefaultWriter + } + + notlogged := conf.SkipPathes + isTerm := true if w, ok := out.(*os.File); !ok || @@ -85,34 +162,27 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { // Log only when path is not being skipped if _, ok := skip[path]; !ok { - // Stop timer - end := time.Now() - latency := end.Sub(start) - - clientIP := c.ClientIP() - method := c.Request.Method - statusCode := c.Writer.Status() - var statusColor, methodColor, resetColor string - if isTerm { - statusColor = colorForStatus(statusCode) - methodColor = colorForMethod(method) - resetColor = reset + param := LogFormatterParams{ + Request: c.Request, + IsTerm: isTerm, } - comment := c.Errors.ByType(ErrorTypePrivate).String() + + // Stop timer + param.TimeStamp = time.Now() + param.Latency = param.TimeStamp.Sub(start) + + param.ClientIP = c.ClientIP() + param.Method = c.Request.Method + param.StatusCode = c.Writer.Status() + param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() if raw != "" { path = path + "?" + raw } - fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", - end.Format("2006/01/02 - 15:04:05"), - statusColor, statusCode, resetColor, - latency, - clientIP, - methodColor, method, resetColor, - path, - comment, - ) + param.Path = path + + fmt.Fprintf(out, formatter(param)) } } } diff --git a/logger_test.go b/logger_test.go index 6118cb04..909ddd39 100644 --- a/logger_test.go +++ b/logger_test.go @@ -7,8 +7,10 @@ package gin import ( "bytes" "errors" + "fmt" "net/http" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -79,7 +81,179 @@ func TestLogger(t *testing.T) { assert.Contains(t, buffer.String(), "404") assert.Contains(t, buffer.String(), "GET") assert.Contains(t, buffer.String(), "/notfound") +} +func TestLoggerWithConfig(t *testing.T) { + buffer := new(bytes.Buffer) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{Output: buffer})) + router.GET("/example", func(c *Context) {}) + router.POST("/example", func(c *Context) {}) + router.PUT("/example", func(c *Context) {}) + router.DELETE("/example", func(c *Context) {}) + router.PATCH("/example", func(c *Context) {}) + router.HEAD("/example", func(c *Context) {}) + router.OPTIONS("/example", func(c *Context) {}) + + performRequest(router, "GET", "/example?a=100") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") + + // I wrote these first (extending the above) but then realized they are more + // like integration tests because they test the whole logging process rather + // than individual functions. Im not sure where these should go. + buffer.Reset() + performRequest(router, "POST", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "POST") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "PUT", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "PUT") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "DELETE", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "DELETE") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "PATCH", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "PATCH") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "HEAD", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "HEAD") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "OPTIONS", "/example") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "OPTIONS") + assert.Contains(t, buffer.String(), "/example") + + buffer.Reset() + performRequest(router, "GET", "/notfound") + assert.Contains(t, buffer.String(), "404") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/notfound") +} + +func TestLoggerWithFormatter(t *testing.T) { + buffer := new(bytes.Buffer) + + d := DefaultWriter + DefaultWriter = buffer + defer func() { + DefaultWriter = d + }() + + router := New() + router.Use(LoggerWithFormatter(func(param LogFormatterParams) string { + return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s", + param.TimeStamp.Format("2006/01/02 - 15:04:05"), + param.StatusCode, + param.Latency, + param.ClientIP, + param.Method, + param.Path, + param.ErrorMessage, + ) + })) + router.GET("/example", func(c *Context) {}) + performRequest(router, "GET", "/example?a=100") + + // output test + assert.Contains(t, buffer.String(), "[FORMATTER TEST]") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") +} + +func TestLoggerWithConfigFormatting(t *testing.T) { + var gotParam LogFormatterParams + buffer := new(bytes.Buffer) + + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + Formatter: func(param LogFormatterParams) string { + // for assert test + gotParam = param + + return fmt.Sprintf("[FORMATTER TEST] %v | %3d | %13v | %15s | %-7s %s\n%s", + param.TimeStamp.Format("2006/01/02 - 15:04:05"), + param.StatusCode, + param.Latency, + param.ClientIP, + param.Method, + param.Path, + param.ErrorMessage, + ) + }, + })) + router.GET("/example", func(c *Context) { + // set dummy ClientIP + c.Request.Header.Set("X-Forwarded-For", "20.20.20.20") + }) + performRequest(router, "GET", "/example?a=100") + + // output test + assert.Contains(t, buffer.String(), "[FORMATTER TEST]") + assert.Contains(t, buffer.String(), "200") + assert.Contains(t, buffer.String(), "GET") + assert.Contains(t, buffer.String(), "/example") + assert.Contains(t, buffer.String(), "a=100") + + // LogFormatterParams test + assert.NotNil(t, gotParam.Request) + assert.NotEmpty(t, gotParam.TimeStamp) + assert.Equal(t, 200, gotParam.StatusCode) + assert.NotEmpty(t, gotParam.Latency) + assert.Equal(t, "20.20.20.20", gotParam.ClientIP) + assert.Equal(t, "GET", gotParam.Method) + assert.Equal(t, "/example?a=100", gotParam.Path) + assert.Empty(t, gotParam.ErrorMessage) + +} + +func TestDefaultLogFormatter(t *testing.T) { + timeStamp := time.Unix(1544173902, 0).UTC() + + termFalseParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Second * 5, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + IsTerm: false, + } + + termTrueParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Second * 5, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + IsTerm: true, + } + + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) + + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam)) } func TestColorForMethod(t *testing.T) { @@ -127,7 +301,7 @@ func TestErrorLogger(t *testing.T) { assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) } -func TestSkippingPaths(t *testing.T) { +func TestLoggerWithWriterSkippingPaths(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithWriter(buffer, "/skipped")) @@ -142,6 +316,24 @@ func TestSkippingPaths(t *testing.T) { assert.Contains(t, buffer.String(), "") } +func TestLoggerWithConfigSkippingPaths(t *testing.T) { + buffer := new(bytes.Buffer) + router := New() + router.Use(LoggerWithConfig(LoggerConfig{ + Output: buffer, + SkipPathes: []string{"/skipped"}, + })) + router.GET("/logged", func(c *Context) {}) + router.GET("/skipped", func(c *Context) {}) + + performRequest(router, "GET", "/logged") + assert.Contains(t, buffer.String(), "200") + + buffer.Reset() + performRequest(router, "GET", "/skipped") + assert.Contains(t, buffer.String(), "") +} + func TestDisableConsoleColor(t *testing.T) { New() assert.False(t, disableColor) From 59695e7ba86dec1a8d847c3329a3e7c9f7705125 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 12 Dec 2018 23:40:29 +0800 Subject: [PATCH 02/23] Add BindUri (#1694) * add BindUri * fix bug * fix code style --- context.go | 18 ++++++++++++++---- githubapi_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index 478e8c09..c94926e1 100644 --- a/context.go +++ b/context.go @@ -530,15 +530,25 @@ func (c *Context) BindYAML(obj interface{}) error { return c.MustBindWith(obj, binding.YAML) } +// BindUri binds the passed struct pointer using binding.Uri. +// It will abort the request with HTTP 400 if any error occurs. +func (c *Context) BindUri(obj interface{}) error { + if err := c.ShouldBindUri(obj); err != nil { + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + return err + } + return nil +} + // MustBindWith binds the passed struct pointer using the specified binding engine. // It will abort the request with HTTP 400 if any error occurs. // See the binding package. -func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { - if err = c.ShouldBindWith(obj, b); err != nil { +func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { + if err := c.ShouldBindWith(obj, b); err != nil { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + return err } - - return + return nil } // ShouldBind checks the Content-Type to select a binding engine automatically, diff --git a/githubapi_test.go b/githubapi_test.go index 6b56a2b7..5253425a 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -290,8 +290,8 @@ func TestShouldBindUri(t *testing.T) { router := Default() type Person struct { - Name string `uri:"name"` - Id string `uri:"id"` + Name string `uri:"name" binding:"required"` + Id string `uri:"id" binding:"required"` } router.Handle("GET", "/rest/:name/:id", func(c *Context) { var person Person @@ -304,6 +304,46 @@ func TestShouldBindUri(t *testing.T) { path, _ := exampleFromPath("/rest/:name/:id") w := performRequest(router, "GET", path) assert.Equal(t, "ShouldBindUri test OK", w.Body.String()) + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestBindUri(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + + type Person struct { + Name string `uri:"name" binding:"required"` + Id string `uri:"id" binding:"required"` + } + router.Handle("GET", "/rest/:name/:id", func(c *Context) { + var person Person + assert.NoError(t, c.BindUri(&person)) + assert.True(t, "" != person.Name) + assert.True(t, "" != person.Id) + c.String(http.StatusOK, "BindUri test OK") + }) + + path, _ := exampleFromPath("/rest/:name/:id") + w := performRequest(router, "GET", path) + assert.Equal(t, "BindUri test OK", w.Body.String()) + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestBindUriError(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + + type Member struct { + Number string `uri:"num" binding:"required,uuid"` + } + router.Handle("GET", "/new/rest/:num", func(c *Context) { + var m Member + c.BindUri(&m) + }) + + path1, _ := exampleFromPath("/new/rest/:num") + w1 := performRequest(router, "GET", path1) + assert.Equal(t, http.StatusBadRequest, w1.Code) } func githubConfigRouter(router *Engine) { From f67d7a90c4d2e5bdf310a78d7e6a04e3d9aee851 Mon Sep 17 00:00:00 2001 From: Romain Beuque Date: Thu, 13 Dec 2018 12:20:17 +0900 Subject: [PATCH 03/23] context: inherits context cancelation and deadline from http.Request context for Go>=1.7 (#1690) *gin.Context implements standard context.Context methods, but always returns data as context is still valid. Since Go 1.7, http.Request now contains a context.Context object, which can be controlled by the http.Server to indicates that the context is now closed, and persue of request should be canceled. This implements the propagation of http.Request context methods inside gin.Context to have HTTP context cancelation information at gin.Context level. Signed-off-by: Romain Beuque --- context.go | 28 -------------------------- context_17.go | 30 ++++++++++++++++++++++++++++ context_17_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ context_pre17.go | 39 ++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 28 deletions(-) create mode 100644 context_pre17.go diff --git a/context.go b/context.go index c94926e1..c38b2b87 100644 --- a/context.go +++ b/context.go @@ -942,34 +942,6 @@ func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. -func (c *Context) Deadline() (deadline time.Time, ok bool) { - return -} - -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. -func (c *Context) Done() <-chan struct{} { - return nil -} - -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. -func (c *Context) Err() error { - return nil -} - // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. diff --git a/context_17.go b/context_17.go index 8e9f75ad..024dcb70 100644 --- a/context_17.go +++ b/context_17.go @@ -7,6 +7,8 @@ package gin import ( + "time" + "github.com/gin-gonic/gin/render" ) @@ -15,3 +17,31 @@ import ( func (c *Context) PureJSON(code int, obj interface{}) { c.Render(code, render.PureJSON{Data: obj}) } + +/************************************/ +/***** GOLANG.ORG/X/NET/CONTEXT *****/ +/************************************/ + +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. +func (c *Context) Deadline() (time.Time, bool) { + return c.Request.Context().Deadline() +} + +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. +func (c *Context) Done() <-chan struct{} { + return c.Request.Context().Done() +} + +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. +func (c *Context) Err() error { + return c.Request.Context().Err() +} diff --git a/context_17_test.go b/context_17_test.go index 5b9ebcdc..f2a2f184 100644 --- a/context_17_test.go +++ b/context_17_test.go @@ -7,9 +7,12 @@ package gin import ( + "bytes" + "context" "net/http" "net/http/httptest" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -25,3 +28,49 @@ func TestContextRenderPureJSON(t *testing.T) { assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } + +func TestContextHTTPContext(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + c.Request = req.WithContext(ctx) + + assert.NoError(t, c.Err()) + assert.NotNil(t, c.Done()) + select { + case <-c.Done(): + assert.Fail(t, "context should not be canceled") + default: + } + + ti, ok := c.Deadline() + assert.Equal(t, ti, time.Time{}) + assert.False(t, ok) + assert.Equal(t, c.Value(0), c.Request) + + cancelFunc() + assert.NotNil(t, c.Done()) + select { + case <-c.Done(): + default: + assert.Fail(t, "context should be canceled") + } +} + +func TestContextHTTPContextWithDeadline(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + location, _ := time.LoadLocation("Europe/Paris") + assert.NotNil(t, location) + date := time.Date(2031, 12, 27, 16, 00, 00, 00, location) + ctx, cancelFunc := context.WithDeadline(context.Background(), date) + defer cancelFunc() + c.Request = req.WithContext(ctx) + + assert.NoError(t, c.Err()) + + ti, ok := c.Deadline() + assert.Equal(t, ti, date) + assert.True(t, ok) +} diff --git a/context_pre17.go b/context_pre17.go new file mode 100644 index 00000000..2008d3c6 --- /dev/null +++ b/context_pre17.go @@ -0,0 +1,39 @@ +// Copyright 2018 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +// +build !go1.7 + +package gin + +import ( + "time" +) + +/************************************/ +/***** GOLANG.ORG/X/NET/CONTEXT *****/ +/************************************/ + +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. +func (c *Context) Deadline() (deadline time.Time, ok bool) { + return +} + +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. +func (c *Context) Done() <-chan struct{} { + return nil +} + +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. +func (c *Context) Err() error { + return nil +} From 1542eff27f7d67ef56543358e2f623eb4ea8adf9 Mon Sep 17 00:00:00 2001 From: Ganlv Date: Mon, 17 Dec 2018 08:13:07 +0800 Subject: [PATCH 04/23] Fix #1693: file.Filename should not be trusted (#1699) --- README.md | 4 ++++ examples/upload-file/multiple/main.go | 4 +++- examples/upload-file/single/main.go | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1f902a9..2dc9e5ff 100644 --- a/README.md +++ b/README.md @@ -364,6 +364,10 @@ ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](examples/upload-file/single). +`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) + +> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. + ```go func main() { router := gin.Default() diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go index a55325ed..2b9d6d91 100644 --- a/examples/upload-file/multiple/main.go +++ b/examples/upload-file/multiple/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/http" + "path/filepath" "github.com/gin-gonic/gin" ) @@ -25,7 +26,8 @@ func main() { files := form.File["files"] for _, file := range files { - if err := c.SaveUploadedFile(file, file.Filename); err != nil { + filename := filepath.Base(file.Filename) + if err := c.SaveUploadedFile(file, filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go index 5d438651..ba289f54 100644 --- a/examples/upload-file/single/main.go +++ b/examples/upload-file/single/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/http" + "path/filepath" "github.com/gin-gonic/gin" ) @@ -23,7 +24,8 @@ func main() { return } - if err := c.SaveUploadedFile(file, file.Filename); err != nil { + filename := filepath.Base(file.Filename) + if err := c.SaveUploadedFile(file, filename); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) return } From 678e09c736505225e28d8c585087b84faaf4bb80 Mon Sep 17 00:00:00 2001 From: Sai Date: Thu, 20 Dec 2018 18:54:08 +0900 Subject: [PATCH 05/23] Plural is "Paths", not "Pathes" (#1706) --- logger.go | 10 +++++----- logger_test.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/logger.go b/logger.go index a64af697..b9c63c73 100644 --- a/logger.go +++ b/logger.go @@ -35,9 +35,9 @@ type LoggerConfig struct { // Optional. Default value is gin.DefaultWriter. Output io.Writer - // SkipPathes is a url path array which logs are not written. + // SkipPaths is a url path array which logs are not written. // Optional. - SkipPathes []string + SkipPaths []string } // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter @@ -114,8 +114,8 @@ func LoggerWithFormatter(f LogFormatter) HandlerFunc { // Example: os.Stdout, a file opened in write mode, a socket... func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { return LoggerWithConfig(LoggerConfig{ - Output: out, - SkipPathes: notlogged, + Output: out, + SkipPaths: notlogged, }) } @@ -131,7 +131,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { out = DefaultWriter } - notlogged := conf.SkipPathes + notlogged := conf.SkipPaths isTerm := true diff --git a/logger_test.go b/logger_test.go index 909ddd39..350599d4 100644 --- a/logger_test.go +++ b/logger_test.go @@ -320,8 +320,8 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { buffer := new(bytes.Buffer) router := New() router.Use(LoggerWithConfig(LoggerConfig{ - Output: buffer, - SkipPathes: []string{"/skipped"}, + Output: buffer, + SkipPaths: []string{"/skipped"}, })) router.GET("/logged", func(c *Context) {}) router.GET("/skipped", func(c *Context) {}) From 2d33c82028b4085827137c5a72cc0a076d8e2b08 Mon Sep 17 00:00:00 2001 From: Sai Date: Wed, 26 Dec 2018 00:27:24 +0900 Subject: [PATCH 06/23] Add comment to LogFormatterParams struct's fields (#1711) By https://github.com/gin-gonic/gin/issues/1701, I thought it's necessary. --- logger.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/logger.go b/logger.go index b9c63c73..a55d26e0 100644 --- a/logger.go +++ b/logger.go @@ -45,15 +45,24 @@ type LogFormatter func(params LogFormatterParams) string // LogFormatterParams is the structure any formatter will be handed when time to log comes type LogFormatterParams struct { - Request *http.Request - TimeStamp time.Time - StatusCode int - Latency time.Duration - ClientIP string - Method string - Path string + Request *http.Request + + // TimeStamp shows the time after the server returns a response. + TimeStamp time.Time + // StatusCode is HTTP response code. + StatusCode int + // Latency is how much time the server cost to process a certain request. + Latency time.Duration + // ClientIP equals Context's ClientIP method. + ClientIP string + // Method is the HTTP method given to the request. + Method string + // Path is a path the client requests. + Path string + // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string - IsTerm bool + // IsTerm shows whether does gin's output descriptor refers to a terminal. + IsTerm bool } // defaultLogFormatter is the default log format function Logger middleware uses. From 1b34e8e8de41654004dfabf20fbadf43f619d41b Mon Sep 17 00:00:00 2001 From: thinkerou Date: Tue, 25 Dec 2018 23:40:11 +0800 Subject: [PATCH 07/23] chore: attemp to fix #1700 (#1707) --- tools.go => tools/tools.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tools.go => tools/tools.go (88%) diff --git a/tools.go b/tools/tools.go similarity index 88% rename from tools.go rename to tools/tools.go index 9f96406a..7113e71e 100644 --- a/tools.go +++ b/tools/tools.go @@ -4,12 +4,12 @@ // +build tools -// This file exists to cause `go mod` and `go get` to believe these tools +// This package exists to cause `go mod` and `go get` to believe these tools // are dependencies, even though they are not runtime dependencies of any // gin package. This means they will appear in `go.mod` file, but will not // be a part of the build. -package gin +package tools import ( _ "github.com/campoy/embedmd" From 0bfc9cbcdbaa13e5fd633f77a32f22c30f1e2553 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 26 Dec 2018 00:27:46 +0800 Subject: [PATCH 08/23] ci: exit 1 when build fail (#1695) Like this: ``` FAIL github.com/gin-gonic/gin [build failed] ``` --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b0d2e24a..7211144a 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,10 @@ test: cat tmp.out; \ if grep -q "^--- FAIL" tmp.out; then \ rm tmp.out; \ - exit 1;\ + exit 1; \ + elif grep -q "build failed" tmp.out; then \ + rm tmp.out; \ + exit; \ fi; \ if [ -f profile.out ]; then \ cat profile.out | grep -v "mode:" >> coverage.out; \ From 49e4b0c60cb533e943c34a8d637944f25fa47ee6 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 28 Dec 2018 04:57:09 +0300 Subject: [PATCH 09/23] fix mapping inner structs with correct tag (#1718) --- binding/binding_test.go | 23 +++++++++++++++++++++++ binding/form_mapping.go | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index c0204d7f..740749be 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -11,6 +11,7 @@ import ( "io/ioutil" "mime/multipart" "net/http" + "strconv" "testing" "time" @@ -690,6 +691,28 @@ func TestUriBinding(t *testing.T) { assert.Equal(t, map[string]interface{}(nil), not.Name) } +func TestUriInnerBinding(t *testing.T) { + type Tag struct { + Name string `uri:"name"` + S struct { + Age int `uri:"age"` + } + } + + expectedName := "mike" + expectedAge := 25 + + m := map[string][]string{ + "name": {expectedName}, + "age": {strconv.Itoa(expectedAge)}, + } + + var tag Tag + assert.NoError(t, Uri.BindUri(m, &tag)) + assert.Equal(t, tag.Name, expectedName) + assert.Equal(t, tag.S.Age, expectedAge) +} + func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index d893c21c..8900ab70 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -55,7 +55,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { structFieldKind = structField.Kind() } if structFieldKind == reflect.Struct { - err := mapForm(structField.Addr().Interface(), form) + err := mapFormByTag(structField.Addr().Interface(), form, tag) if err != nil { return err } From 807368579f8939cedfa59ec689708754920f93e4 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 28 Dec 2018 05:26:29 +0300 Subject: [PATCH 10/23] fix test - auto choose port number (#1719) --- gin_integration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index e14a688c..01d5cf5e 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -137,7 +137,7 @@ func TestBadUnixSocket(t *testing.T) { func TestFileDescriptor(t *testing.T) { router := New() - addr, err := net.ResolveTCPAddr("tcp", ":8000") + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") assert.NoError(t, err) listener, err := net.ListenTCP("tcp", addr) assert.NoError(t, err) @@ -152,7 +152,7 @@ func TestFileDescriptor(t *testing.T) { // otherwise the main thread will complete time.Sleep(5 * time.Millisecond) - c, err := net.Dial("tcp", "localhost:8000") + c, err := net.Dial("tcp", listener.Addr().String()) assert.NoError(t, err) fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n") From 85b92cdf4bc9bf33fd6f199ff866a1eed3511c80 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 29 Dec 2018 11:46:26 +0800 Subject: [PATCH 11/23] chore(testing): case sensitive for query string (#1720) fix #1692 --- context_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index dced73fd..836b3ecb 100644 --- a/context_test.go +++ b/context_test.go @@ -1457,7 +1457,7 @@ func TestContextShouldBindWithXML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(` FOO - BAR + BAR `)) c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type @@ -1475,15 +1475,19 @@ func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo&Foo=bar1&Bar=foo1", bytes.NewBufferString("foo=unused")) var obj struct { - Foo string `form:"foo"` - Bar string `form:"bar"` + Foo string `form:"foo"` + Bar string `form:"bar"` + Foo1 string `form:"Foo"` + Bar1 string `form:"Bar"` } assert.NoError(t, c.ShouldBindQuery(&obj)) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo1", obj.Bar1) + assert.Equal(t, "bar1", obj.Foo1) assert.Equal(t, 0, w.Body.Len()) } From d8fb18c33b1657271b6302e0899d033902012f49 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Mon, 31 Dec 2018 11:02:53 +1000 Subject: [PATCH 12/23] Fix case of GitHub (#1726) --- routergroup.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 2b41dfda..297d3574 100644 --- a/routergroup.go +++ b/routergroup.go @@ -47,7 +47,7 @@ type RouterGroup struct { var _ IRouter = &RouterGroup{} -// Use adds middleware to the group, see example code in github. +// Use adds middleware to the group, see example code in GitHub. func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() @@ -78,7 +78,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl // Handle registers a new request handle and middleware with the given path and method. // The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes. -// See the example code in github. +// See the example code in GitHub. // // For GET, POST, PUT, PATCH and DELETE requests the respective shortcut // functions can be used. From 29a145c85dc0fafc3dd0ada62d856c4d95240010 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 9 Jan 2019 09:32:44 +0800 Subject: [PATCH 13/23] Revert "context: inherits context cancelation and deadline from http.Request context for Go>=1.7 (#1690)" (#1736) This reverts commit f67d7a90c4d2e5bdf310a78d7e6a04e3d9aee851. --- context.go | 28 ++++++++++++++++++++++++++ context_17.go | 30 ---------------------------- context_17_test.go | 49 ---------------------------------------------- context_pre17.go | 39 ------------------------------------ 4 files changed, 28 insertions(+), 118 deletions(-) delete mode 100644 context_pre17.go diff --git a/context.go b/context.go index c38b2b87..c94926e1 100644 --- a/context.go +++ b/context.go @@ -942,6 +942,34 @@ func (c *Context) SetAccepted(formats ...string) { c.Accepted = formats } +/************************************/ +/***** GOLANG.ORG/X/NET/CONTEXT *****/ +/************************************/ + +// Deadline returns the time when work done on behalf of this context +// should be canceled. Deadline returns ok==false when no deadline is +// set. Successive calls to Deadline return the same results. +func (c *Context) Deadline() (deadline time.Time, ok bool) { + return +} + +// Done returns a channel that's closed when work done on behalf of this +// context should be canceled. Done may return nil if this context can +// never be canceled. Successive calls to Done return the same value. +func (c *Context) Done() <-chan struct{} { + return nil +} + +// Err returns a non-nil error value after Done is closed, +// successive calls to Err return the same error. +// If Done is not yet closed, Err returns nil. +// If Done is closed, Err returns a non-nil error explaining why: +// Canceled if the context was canceled +// or DeadlineExceeded if the context's deadline passed. +func (c *Context) Err() error { + return nil +} + // Value returns the value associated with this context for key, or nil // if no value is associated with key. Successive calls to Value with // the same key returns the same result. diff --git a/context_17.go b/context_17.go index 024dcb70..8e9f75ad 100644 --- a/context_17.go +++ b/context_17.go @@ -7,8 +7,6 @@ package gin import ( - "time" - "github.com/gin-gonic/gin/render" ) @@ -17,31 +15,3 @@ import ( func (c *Context) PureJSON(code int, obj interface{}) { c.Render(code, render.PureJSON{Data: obj}) } - -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. -func (c *Context) Deadline() (time.Time, bool) { - return c.Request.Context().Deadline() -} - -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. -func (c *Context) Done() <-chan struct{} { - return c.Request.Context().Done() -} - -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. -func (c *Context) Err() error { - return c.Request.Context().Err() -} diff --git a/context_17_test.go b/context_17_test.go index f2a2f184..5b9ebcdc 100644 --- a/context_17_test.go +++ b/context_17_test.go @@ -7,12 +7,9 @@ package gin import ( - "bytes" - "context" "net/http" "net/http/httptest" "testing" - "time" "github.com/stretchr/testify/assert" ) @@ -28,49 +25,3 @@ func TestContextRenderPureJSON(t *testing.T) { assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } - -func TestContextHTTPContext(t *testing.T) { - c, _ := CreateTestContext(httptest.NewRecorder()) - req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) - ctx, cancelFunc := context.WithCancel(context.Background()) - defer cancelFunc() - c.Request = req.WithContext(ctx) - - assert.NoError(t, c.Err()) - assert.NotNil(t, c.Done()) - select { - case <-c.Done(): - assert.Fail(t, "context should not be canceled") - default: - } - - ti, ok := c.Deadline() - assert.Equal(t, ti, time.Time{}) - assert.False(t, ok) - assert.Equal(t, c.Value(0), c.Request) - - cancelFunc() - assert.NotNil(t, c.Done()) - select { - case <-c.Done(): - default: - assert.Fail(t, "context should be canceled") - } -} - -func TestContextHTTPContextWithDeadline(t *testing.T) { - c, _ := CreateTestContext(httptest.NewRecorder()) - req, _ := http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) - location, _ := time.LoadLocation("Europe/Paris") - assert.NotNil(t, location) - date := time.Date(2031, 12, 27, 16, 00, 00, 00, location) - ctx, cancelFunc := context.WithDeadline(context.Background(), date) - defer cancelFunc() - c.Request = req.WithContext(ctx) - - assert.NoError(t, c.Err()) - - ti, ok := c.Deadline() - assert.Equal(t, ti, date) - assert.True(t, ok) -} diff --git a/context_pre17.go b/context_pre17.go deleted file mode 100644 index 2008d3c6..00000000 --- a/context_pre17.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build !go1.7 - -package gin - -import ( - "time" -) - -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// Deadline returns the time when work done on behalf of this context -// should be canceled. Deadline returns ok==false when no deadline is -// set. Successive calls to Deadline return the same results. -func (c *Context) Deadline() (deadline time.Time, ok bool) { - return -} - -// Done returns a channel that's closed when work done on behalf of this -// context should be canceled. Done may return nil if this context can -// never be canceled. Successive calls to Done return the same value. -func (c *Context) Done() <-chan struct{} { - return nil -} - -// Err returns a non-nil error value after Done is closed, -// successive calls to Err return the same error. -// If Done is not yet closed, Err returns nil. -// If Done is closed, Err returns a non-nil error explaining why: -// Canceled if the context was canceled -// or DeadlineExceeded if the context's deadline passed. -func (c *Context) Err() error { - return nil -} From b056a34bdc2aa45256c4f5bdad306c35ec70c37e Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 18 Jan 2019 04:32:53 +0300 Subject: [PATCH 14/23] fix errcheck warnings (#1739) --- binding/binding_test.go | 24 +++++++++++----------- binding/form.go | 6 +++++- context.go | 22 +++++++++++++------- context_test.go | 36 +++++++++++++++++++-------------- debug_test.go | 19 ++++++++--------- errors_test.go | 6 +++--- gin.go | 5 ++++- gin_integration_test.go | 2 +- githubapi_test.go | 2 +- logger_test.go | 6 +++--- middleware_test.go | 2 +- recovery.go | 2 +- render/json.go | 45 +++++++++++++++++++++++++++-------------- render/protobuf.go | 4 ++-- render/render_test.go | 4 ++-- render/text.go | 10 ++++----- render/yaml.go | 4 ++-- response_writer_test.go | 3 ++- routes_test.go | 3 ++- 19 files changed, 122 insertions(+), 83 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 740749be..1044e6c2 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -516,28 +516,28 @@ func createFormPostRequestFail() *http.Request { return req } -func createFormMultipartRequest() *http.Request { +func createFormMultipartRequest(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() - mw.SetBoundary(boundary) - mw.WriteField("foo", "bar") - mw.WriteField("bar", "foo") + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req } -func createFormMultipartRequestFail() *http.Request { +func createFormMultipartRequestFail(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) mw := multipart.NewWriter(body) defer mw.Close() - mw.SetBoundary(boundary) - mw.WriteField("map_foo", "bar") + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("map_foo", "bar")) req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) return req @@ -546,7 +546,7 @@ func createFormMultipartRequestFail() *http.Request { func TestBindingFormPost(t *testing.T) { req := createFormPostRequest() var obj FooBarStruct - FormPost.Bind(req, &obj) + assert.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "form-urlencoded", FormPost.Name()) assert.Equal(t, "bar", obj.Foo) @@ -556,7 +556,7 @@ func TestBindingFormPost(t *testing.T) { func TestBindingDefaultValueFormPost(t *testing.T) { req := createDefaultFormPostRequest() var obj FooDefaultBarStruct - FormPost.Bind(req, &obj) + assert.NoError(t, FormPost.Bind(req, &obj)) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "hello", obj.Bar) @@ -570,9 +570,9 @@ func TestBindingFormPostFail(t *testing.T) { } func TestBindingFormMultipart(t *testing.T) { - req := createFormMultipartRequest() + req := createFormMultipartRequest(t) var obj FooBarStruct - FormMultipart.Bind(req, &obj) + assert.NoError(t, FormMultipart.Bind(req, &obj)) assert.Equal(t, "multipart/form-data", FormMultipart.Name()) assert.Equal(t, "bar", obj.Foo) @@ -580,7 +580,7 @@ func TestBindingFormMultipart(t *testing.T) { } func TestBindingFormMultipartFail(t *testing.T) { - req := createFormMultipartRequestFail() + req := createFormMultipartRequestFail(t) var obj FooStructForMapType err := FormMultipart.Bind(req, &obj) assert.Error(t, err) diff --git a/binding/form.go b/binding/form.go index 0be59660..8955c95b 100644 --- a/binding/form.go +++ b/binding/form.go @@ -20,7 +20,11 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } - req.ParseMultipartForm(defaultMemory) + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } if err := mapForm(obj, req.Form); err != nil { return err } diff --git a/context.go b/context.go index c94926e1..4dc94ea9 100644 --- a/context.go +++ b/context.go @@ -415,7 +415,11 @@ func (c *Context) PostFormArray(key string) []string { // a boolean value whether at least one value exists for the given key. func (c *Context) GetPostFormArray(key string) ([]string, bool) { req := c.Request - req.ParseMultipartForm(c.engine.MaxMultipartMemory) + if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + if err != http.ErrNotMultipart { + debugPrint("error on parse multipart form array: %v", err) + } + } if values := req.PostForm[key]; len(values) > 0 { return values, true } @@ -437,7 +441,11 @@ func (c *Context) PostFormMap(key string) map[string]string { // whether at least one value exists for the given key. func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { req := c.Request - req.ParseMultipartForm(c.engine.MaxMultipartMemory) + if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + if err != http.ErrNotMultipart { + debugPrint("error on parse multipart form map: %v", err) + } + } dicts, exist := c.get(req.PostForm, key) if !exist && req.MultipartForm != nil && req.MultipartForm.File != nil { @@ -493,8 +501,8 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error } defer out.Close() - io.Copy(out, src) - return nil + _, err = io.Copy(out, src) + return err } // Bind checks the Content-Type to select a binding engine automatically, @@ -534,7 +542,7 @@ func (c *Context) BindYAML(obj interface{}) error { // It will abort the request with HTTP 400 if any error occurs. func (c *Context) BindUri(obj interface{}) error { if err := c.ShouldBindUri(obj); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err } return nil @@ -545,7 +553,7 @@ func (c *Context) BindUri(obj interface{}) error { // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { if err := c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) + c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err } return nil @@ -913,7 +921,7 @@ func (c *Context) Negotiate(code int, config Negotiate) { c.XML(code, data) default: - c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) + c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck } } diff --git a/context_test.go b/context_test.go index 836b3ecb..29ec1a24 100644 --- a/context_test.go +++ b/context_test.go @@ -70,7 +70,8 @@ func TestContextFormFile(t *testing.T) { mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { - w.Write([]byte("test")) + _, err = w.Write([]byte("test")) + assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) @@ -100,10 +101,11 @@ func TestContextFormFileFailed(t *testing.T) { func TestContextMultipartForm(t *testing.T) { buf := new(bytes.Buffer) mw := multipart.NewWriter(buf) - mw.WriteField("foo", "bar") + assert.NoError(t, mw.WriteField("foo", "bar")) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { - w.Write([]byte("test")) + _, err = w.Write([]byte("test")) + assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) @@ -137,7 +139,8 @@ func TestSaveUploadedCreateFailed(t *testing.T) { mw := multipart.NewWriter(buf) w, err := mw.CreateFormFile("file", "test") if assert.NoError(t, err) { - w.Write([]byte("test")) + _, err = w.Write([]byte("test")) + assert.NoError(t, err) } mw.Close() c, _ := CreateTestContext(httptest.NewRecorder()) @@ -159,7 +162,7 @@ func TestContextReset(t *testing.T) { c.index = 2 c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()} c.Params = Params{Param{}} - c.Error(errors.New("test")) + c.Error(errors.New("test")) // nolint: errcheck c.Set("foo", "bar") c.reset() @@ -798,7 +801,7 @@ func TestContextRenderHTML2(t *testing.T) { assert.Len(t, router.trees, 1) templ := template.Must(template.New("t").Parse(`Hello {{.name}}`)) - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) router.SetHTMLTemplate(templ) SetMode(TestMode) @@ -1211,7 +1214,8 @@ func TestContextAbortWithStatusJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", contentType) buf := new(bytes.Buffer) - buf.ReadFrom(w.Body) + _, err := buf.ReadFrom(w.Body) + assert.NoError(t, err) jsonStringBody := buf.String() assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody) } @@ -1220,11 +1224,11 @@ func TestContextError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) - c.Error(errors.New("first error")) + c.Error(errors.New("first error")) // nolint: errcheck assert.Len(t, c.Errors, 1) assert.Equal(t, "Error #01: first error\n", c.Errors.String()) - c.Error(&Error{ + c.Error(&Error{ // nolint: errcheck Err: errors.New("second error"), Meta: "some data 2", Type: ErrorTypePublic, @@ -1246,13 +1250,13 @@ func TestContextError(t *testing.T) { t.Error("didn't panic") } }() - c.Error(nil) + c.Error(nil) // nolint: errcheck } func TestContextTypedError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) - c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) - c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) + c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) // nolint: errcheck + c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) // nolint: errcheck for _, err := range c.Errors.ByType(ErrorTypePublic) { assert.Equal(t, ErrorTypePublic, err.Type) @@ -1267,7 +1271,7 @@ func TestContextAbortWithError(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) - c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") + c.AbortWithError(http.StatusUnauthorized, errors.New("bad input")).SetMeta("some input") // nolint: errcheck assert.Equal(t, http.StatusUnauthorized, w.Code) assert.Equal(t, abortIndex, c.index) @@ -1713,7 +1717,8 @@ func TestContextStream(t *testing.T) { stopStream = false }() - w.Write([]byte("test")) + _, err := w.Write([]byte("test")) + assert.NoError(t, err) return stopStream }) @@ -1730,7 +1735,8 @@ func TestContextStreamWithClientGone(t *testing.T) { w.closeClient() }() - writer.Write([]byte("test")) + _, err := writer.Write([]byte("test")) + assert.NoError(t, err) return true }) diff --git a/debug_test.go b/debug_test.go index 97ff166b..b3485d70 100644 --- a/debug_test.go +++ b/debug_test.go @@ -32,7 +32,7 @@ func TestIsDebugging(t *testing.T) { } func TestDebugPrint(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) SetMode(ReleaseMode) debugPrint("DEBUG this!") @@ -46,7 +46,7 @@ func TestDebugPrint(t *testing.T) { } func TestDebugPrintError(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintError(nil) debugPrintError(errors.New("this is an error")) @@ -56,7 +56,7 @@ func TestDebugPrintError(t *testing.T) { } func TestDebugPrintRoutes(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintRoute("GET", "/path/to/route/:param", HandlersChain{func(c *Context) {}, handlerNameTest}) SetMode(TestMode) @@ -65,7 +65,7 @@ func TestDebugPrintRoutes(t *testing.T) { } func TestDebugPrintLoadTemplate(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) templ := template.Must(template.New("").Delims("{[{", "}]}").ParseGlob("./testdata/template/hello.tmpl")) debugPrintLoadTemplate(templ) @@ -75,7 +75,7 @@ func TestDebugPrintLoadTemplate(t *testing.T) { } func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGSetHTMLTemplate() SetMode(TestMode) @@ -84,7 +84,7 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { } func TestDebugPrintWARNINGDefault(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGDefault() SetMode(TestMode) @@ -98,7 +98,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { } func TestDebugPrintWARNINGNew(t *testing.T) { - re := captureOutput(func() { + re := captureOutput(t, func() { SetMode(DebugMode) debugPrintWARNINGNew() SetMode(TestMode) @@ -106,7 +106,7 @@ func TestDebugPrintWARNINGNew(t *testing.T) { assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", re) } -func captureOutput(f func()) string { +func captureOutput(t *testing.T, f func()) string { reader, writer, err := os.Pipe() if err != nil { panic(err) @@ -127,7 +127,8 @@ func captureOutput(f func()) string { go func() { var buf bytes.Buffer wg.Done() - io.Copy(&buf, reader) + _, err := io.Copy(&buf, reader) + assert.NoError(t, err) out <- buf.String() }() wg.Wait() diff --git a/errors_test.go b/errors_test.go index 9351b578..6aae1c10 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,7 +34,7 @@ func TestError(t *testing.T) { jsonBytes, _ := json.Marshal(err) assert.Equal(t, "{\"error\":\"test error\",\"meta\":\"some data\"}", string(jsonBytes)) - err.SetMeta(H{ + err.SetMeta(H{ // nolint: errcheck "status": "200", "data": "some data", }) @@ -44,7 +44,7 @@ func TestError(t *testing.T) { "data": "some data", }, err.JSON()) - err.SetMeta(H{ + err.SetMeta(H{ // nolint: errcheck "error": "custom error", "status": "200", "data": "some data", @@ -59,7 +59,7 @@ func TestError(t *testing.T) { status string data string } - err.SetMeta(customError{status: "200", data: "other data"}) + err.SetMeta(customError{status: "200", data: "other data"}) // nolint: errcheck assert.Equal(t, customError{status: "200", data: "other data"}, err.JSON()) } diff --git a/gin.go b/gin.go index b7c77e1f..cd47200f 100644 --- a/gin.go +++ b/gin.go @@ -422,7 +422,10 @@ func serveError(c *Context, code int, defaultMessage []byte) { } if c.writermem.Status() == code { c.writermem.Header()["Content-Type"] = mimePlain - c.Writer.Write(defaultMessage) + _, err := c.Writer.Write(defaultMessage) + if err != nil { + debugPrint("cannot write message to writer during serve error: %v", err) + } return } c.writermem.WriteHeaderNow() diff --git a/gin_integration_test.go b/gin_integration_test.go index 01d5cf5e..3ce6d80a 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -87,7 +87,7 @@ func TestRunEmptyWithEnv(t *testing.T) { func TestRunTooMuchParams(t *testing.T) { router := New() assert.Panics(t, func() { - router.Run("2", "2") + assert.NoError(t, router.Run("2", "2")) }) } diff --git a/githubapi_test.go b/githubapi_test.go index 5253425a..50faca09 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -338,7 +338,7 @@ func TestBindUriError(t *testing.T) { } router.Handle("GET", "/new/rest/:num", func(c *Context) { var m Member - c.BindUri(&m) + assert.Error(t, c.BindUri(&m)) }) path1, _ := exampleFromPath("/new/rest/:num") diff --git a/logger_test.go b/logger_test.go index 350599d4..d0169251 100644 --- a/logger_test.go +++ b/logger_test.go @@ -278,13 +278,13 @@ func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) router.GET("/error", func(c *Context) { - c.Error(errors.New("this is an error")) + c.Error(errors.New("this is an error")) // nolint: errcheck }) router.GET("/abort", func(c *Context) { - c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) + c.AbortWithError(http.StatusUnauthorized, errors.New("no authorized")) // nolint: errcheck }) router.GET("/print", func(c *Context) { - c.Error(errors.New("this is an error")) + c.Error(errors.New("this is an error")) // nolint: errcheck c.String(http.StatusInternalServerError, "hola!") }) diff --git a/middleware_test.go b/middleware_test.go index 983ad933..fca1c530 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -208,7 +208,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) { router := New() router.Use(func(context *Context) { signature += "A" - context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) + context.AbortWithError(http.StatusInternalServerError, errors.New("foo")) // nolint: errcheck }) router.Use(func(context *Context) { signature += "B" diff --git a/recovery.go b/recovery.go index f06ad56b..0e35968f 100644 --- a/recovery.go +++ b/recovery.go @@ -66,7 +66,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { // If the connection is dead, we can't write a status to it. if brokenPipe { - c.Error(err.(error)) + c.Error(err.(error)) // nolint: errcheck c.Abort() } else { c.AbortWithStatus(http.StatusInternalServerError) diff --git a/render/json.go b/render/json.go index 32d0fc42..c7cf330e 100644 --- a/render/json.go +++ b/render/json.go @@ -67,8 +67,8 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error { if err != nil { return err } - w.Write(jsonBytes) - return nil + _, err = w.Write(jsonBytes) + return err } // Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType. @@ -78,8 +78,8 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { if err != nil { return err } - w.Write(jsonBytes) - return nil + _, err = w.Write(jsonBytes) + return err } // WriteContentType (IndentedJSON) writes JSON ContentType. @@ -96,10 +96,13 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { } // if the jsonBytes is array values if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) { - w.Write([]byte(r.Prefix)) + _, err = w.Write([]byte(r.Prefix)) + if err != nil { + return err + } } - w.Write(jsonBytes) - return nil + _, err = w.Write(jsonBytes) + return err } // WriteContentType (SecureJSON) writes JSON ContentType. @@ -116,15 +119,27 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { } if r.Callback == "" { - w.Write(ret) - return nil + _, err = w.Write(ret) + return err } callback := template.JSEscapeString(r.Callback) - w.Write([]byte(callback)) - w.Write([]byte("(")) - w.Write(ret) - w.Write([]byte(")")) + _, err = w.Write([]byte(callback)) + if err != nil { + return err + } + _, err = w.Write([]byte("(")) + if err != nil { + return err + } + _, err = w.Write(ret) + if err != nil { + return err + } + _, err = w.Write([]byte(")")) + if err != nil { + return err + } return nil } @@ -151,8 +166,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { buffer.WriteString(cvt) } - w.Write(buffer.Bytes()) - return nil + _, err = w.Write(buffer.Bytes()) + return err } // WriteContentType (AsciiJSON) writes JSON ContentType. diff --git a/render/protobuf.go b/render/protobuf.go index 47895072..15aca995 100644 --- a/render/protobuf.go +++ b/render/protobuf.go @@ -26,8 +26,8 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error { return err } - w.Write(bytes) - return nil + _, err = w.Write(bytes) + return err } // WriteContentType (ProtoBuf) writes ProtoBuf ContentType. diff --git a/render/render_test.go b/render/render_test.go index 4c9b180d..3df04a17 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -71,7 +71,7 @@ func TestRenderJSONPanics(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - assert.Panics(t, func() { (JSON{data}).Render(w) }) + assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) }) } func TestRenderIndentedJSON(t *testing.T) { @@ -335,7 +335,7 @@ func TestRenderRedirect(t *testing.T) { } w = httptest.NewRecorder() - assert.Panics(t, func() { data2.Render(w) }) + assert.Panics(t, func() { assert.NoError(t, data2.Render(w)) }) // only improve coverage data2.WriteContentType(w) diff --git a/render/text.go b/render/text.go index 2ea7343c..4e52d4c5 100644 --- a/render/text.go +++ b/render/text.go @@ -20,8 +20,7 @@ var plainContentType = []string{"text/plain; charset=utf-8"} // Render (String) writes data with custom ContentType. func (r String) Render(w http.ResponseWriter) error { - WriteString(w, r.Format, r.Data) - return nil + return WriteString(w, r.Format, r.Data) } // WriteContentType (String) writes Plain ContentType. @@ -30,11 +29,12 @@ func (r String) WriteContentType(w http.ResponseWriter) { } // WriteString writes data according to its format and write custom ContentType. -func WriteString(w http.ResponseWriter, format string, data []interface{}) { +func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) { writeContentType(w, plainContentType) if len(data) > 0 { - fmt.Fprintf(w, format, data...) + _, err = fmt.Fprintf(w, format, data...) return } - io.WriteString(w, format) + _, err = io.WriteString(w, format) + return } diff --git a/render/yaml.go b/render/yaml.go index 33bc3254..0df78360 100644 --- a/render/yaml.go +++ b/render/yaml.go @@ -26,8 +26,8 @@ func (r YAML) Render(w http.ResponseWriter) error { return err } - w.Write(bytes) - return nil + _, err = w.Write(bytes) + return err } // WriteContentType (YAML) writes YAML ContentType for response. diff --git a/response_writer_test.go b/response_writer_test.go index cc5a89dc..a5e111e5 100644 --- a/response_writer_test.go +++ b/response_writer_test.go @@ -103,7 +103,8 @@ func TestResponseWriterHijack(t *testing.T) { w := ResponseWriter(writer) assert.Panics(t, func() { - w.Hijack() + _, _, err := w.Hijack() + assert.NoError(t, err) }) assert.True(t, w.Written()) diff --git a/routes_test.go b/routes_test.go index c4d59725..af4caf7f 100644 --- a/routes_test.go +++ b/routes_test.go @@ -251,7 +251,8 @@ func TestRouteStaticFile(t *testing.T) { t.Error(err) } defer os.Remove(f.Name()) - f.WriteString("Gin Web Framework") + _, err = f.WriteString("Gin Web Framework") + assert.NoError(t, err) f.Close() dir, filename := filepath.Split(f.Name()) From 4867ff9634d1889156587d5add70d2b29c447542 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Fri, 18 Jan 2019 04:57:06 +0300 Subject: [PATCH 15/23] fix Context.Next() - recheck len of handlers every iteration (#1745) * fix Context.Next() - recheck len of handlers every iteration * add tests when Context.reset() can be called inside of handler TestEngineHandleContext TestContextResetInHandler TestRouterStaticFSFileNotFound * Context.Next() - format to while style --- context.go | 3 ++- context_test.go | 12 ++++++++++++ gin_test.go | 17 +++++++++++++++++ routes_test.go | 10 ++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 4dc94ea9..26badfc3 100644 --- a/context.go +++ b/context.go @@ -105,8 +105,9 @@ func (c *Context) Handler() HandlerFunc { // See example in GitHub. func (c *Context) Next() { c.index++ - for s := int8(len(c.handlers)); c.index < s; c.index++ { + for c.index < int8(len(c.handlers)) { c.handlers[c.index](c) + c.index++ } } diff --git a/context_test.go b/context_test.go index 29ec1a24..ea936b85 100644 --- a/context_test.go +++ b/context_test.go @@ -1743,3 +1743,15 @@ func TestContextStreamWithClientGone(t *testing.T) { assert.Equal(t, "test", w.Body.String()) } + +func TestContextResetInHandler(t *testing.T) { + w := CreateTestResponseRecorder() + c, _ := CreateTestContext(w) + + c.handlers = []HandlerFunc{ + func(c *Context) { c.reset() }, + } + assert.NotPanics(t, func() { + c.Next() + }) +} diff --git a/gin_test.go b/gin_test.go index 353c9be1..e013df09 100644 --- a/gin_test.go +++ b/gin_test.go @@ -471,6 +471,23 @@ func TestListOfRoutes(t *testing.T) { }) } +func TestEngineHandleContext(t *testing.T) { + r := New() + r.GET("/", func(c *Context) { + c.Request.URL.Path = "/v2" + r.HandleContext(c) + }) + v2 := r.Group("/v2") + { + v2.GET("/", func(c *Context) {}) + } + + assert.NotPanics(t, func() { + w := performRequest(r, "GET", "/") + assert.Equal(t, 301, w.Code) + }) +} + func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { for _, gotRoute := range gotRoutes { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { diff --git a/routes_test.go b/routes_test.go index af4caf7f..8d50292d 100644 --- a/routes_test.go +++ b/routes_test.go @@ -427,6 +427,16 @@ func TestRouterStaticFSNotFound(t *testing.T) { assert.Equal(t, "non existent", w.Body.String()) } +func TestRouterStaticFSFileNotFound(t *testing.T) { + router := New() + + router.StaticFS("/", http.FileSystem(http.Dir("."))) + + assert.NotPanics(t, func() { + performRequest(router, "GET", "/nonexistent") + }) +} + func TestRouteRawPath(t *testing.T) { route := New() route.UseRawPath = true From b4f51559825a055dcec93fa282c20018d073aaaa Mon Sep 17 00:00:00 2001 From: Sai Date: Sun, 20 Jan 2019 09:39:09 +0900 Subject: [PATCH 16/23] Fix not to pass formatted string to Fprintf's format specifier parameter (#1747) --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index a55d26e0..d2869f51 100644 --- a/logger.go +++ b/logger.go @@ -191,7 +191,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { param.Path = path - fmt.Fprintf(out, formatter(param)) + fmt.Fprint(out, formatter(param)) } } } From f38a3fe65f102dd765e097166e6a41f9e99777f6 Mon Sep 17 00:00:00 2001 From: Ryan <46182144+ryanker@users.noreply.github.com> Date: Sun, 20 Jan 2019 18:27:04 +0800 Subject: [PATCH 17/23] fix password error (#1728) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2dc9e5ff..0dcaa8e9 100644 --- a/README.md +++ b/README.md @@ -620,7 +620,7 @@ func main() { // // // user - // 123 + // 123 // ) router.POST("/loginXML", func(c *gin.Context) { var xml Login From d27685e714cb0b9375e0c9d3ca3df5a6a4945a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sat, 26 Jan 2019 02:28:39 +0800 Subject: [PATCH 18/23] chore: attempt to fix some gomod issue (#1751) #1604 #1566 #1700 #1737 because some dependencies only are used on example i.e. grpc. Or migrate `examples` to gin-gonic/examples`? --- go.mod | 39 +++++++++++++++--------------- go.sum | 64 ++++++++++++++++++-------------------------------- tools/tools.go | 25 -------------------- 3 files changed, 43 insertions(+), 85 deletions(-) delete mode 100644 tools/tools.go diff --git a/go.mod b/go.mod index ef4103fd..54573a8b 100644 --- a/go.mod +++ b/go.mod @@ -1,30 +1,31 @@ module github.com/gin-gonic/gin require ( - github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 - github.com/client9/misspell v0.3.4 - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 - github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 - github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b + github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482 github.com/golang/protobuf v1.2.0 - github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 github.com/json-iterator/go v1.1.5 - github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 github.com/mattn/go-isatty v0.0.4 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/testify v1.2.2 - github.com/thinkerou/favicon v0.1.0 - github.com/ugorji/go v1.1.1 - golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e - golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd - golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f - golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba // indirect - google.golang.org/grpc v1.15.0 + github.com/stretchr/testify v1.3.0 + github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 + golang.org/x/net v0.0.0-20190119204137-ed066c81e75e + golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 + golang.org/x/sys v0.0.0-20190124100055-b90733256f2e // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 - gopkg.in/yaml.v2 v2.2.1 + gopkg.in/yaml.v2 v2.2.2 +) + +exclude ( + github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160 + github.com/client9/misspell v0.3.4 + github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 + github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768 + github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 + github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 + github.com/thinkerou/favicon v0.1.0 + golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b + golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 + google.golang.org/grpc v1.18.0 ) diff --git a/go.sum b/go.sum index 2ef7f13b..95e2b4f6 100644 --- a/go.sum +++ b/go.sum @@ -1,72 +1,54 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296 h1:tRsilif6pbtt+PX6uRoyGd+qR+4ZPucFZLHlc3Ak6z8= -github.com/campoy/embedmd v0.0.0-20171205015432-c59ce00e0296/go.mod h1:/dBk8ICkslPCmyRdn4azP+QvBxL6Eg3EYxUGI9xMMFw= +github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160 h1:HJpuhXOHC4EkXDARsLjmXAV9FhlY6qFDnKI/MJM6eoE= +github.com/campoy/embedmd v0.0.0-20181127031020-97c13d6e4160/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66 h1:QnnoVdChKs+GeTvN4rPYTW6b5U6M3HMEvQ/+x4IGtfY= -github.com/dustin/go-broadcast v0.0.0-20171205050544-f664265f5a66/go.mod h1:kTEh6M2J/mh7nsskr28alwLCXm/DSG5OSA/o31yy2XU= -github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= -github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b h1:dm/NYytoj7p8Jc6zMvyRz3PCQrTTCXnVRvEzyBcM890= -github.com/gin-gonic/autotls v0.0.0-20180426091246-be87bd5ef97b/go.mod h1:vwfeXwKgEIWq63oVfwaBjoByS4dZzYbHHROHjV4IjNY= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482 h1:iOz5sIQUvuOlpiC7Q6+MmJQpWnlneYX98QIGf+2m50Y= +github.com/gin-contrib/sse v0.0.0-20190124093953-61b50c2ef482/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 h1:cW/amwGEJK5MSKntPXRjX4dxs/nGxGT8gXKIsKFmHGc= -github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15/go.mod h1:Fdm/oWRW+CH8PRbLntksCNtmcCBximKPkVQYvmMl80k= -github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 h1:KIaAZ/V+/0/6BOULrmBQ9T1ed8BkKqGIjIKW923nJuo= -github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227/go.mod h1:ruMr5t05gVho4tuDv0PbI0Bb8nOxc/5Y6JzRHe/yfA0= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/thinkerou/favicon v0.1.0 h1:eWMISKTpHq2G8HOuKn7ydD55j5DDehx94b0C2y8ABMs= -github.com/thinkerou/favicon v0.1.0/go.mod h1:HL7Pap5kOluZv1ku34pZo/AJ44GaxMEPFZ3pmuexV2s= -github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= -github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= -golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd h1:cgsAvzdqkDKdI02tIvDjO225vDPHMDCgfKqx5KEVI7U= -golang.org/x/lint v0.0.0-20181011164241-5906bd5c48cd/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY+LsWmuwob+CRS1BmdRdjphAm9mH4= +github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q= +golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc= -golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190124100055-b90733256f2e h1:3GIlrlVLfkoipSReOMNAgApI0ajnalyLa/EZHHca/XI= +golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw= -google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/tools/tools.go b/tools/tools.go deleted file mode 100644 index 7113e71e..00000000 --- a/tools/tools.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build tools - -// This package exists to cause `go mod` and `go get` to believe these tools -// are dependencies, even though they are not runtime dependencies of any -// gin package. This means they will appear in `go.mod` file, but will not -// be a part of the build. - -package tools - -import ( - _ "github.com/campoy/embedmd" - _ "github.com/client9/misspell/cmd/misspell" - _ "github.com/dustin/go-broadcast" - _ "github.com/gin-gonic/autotls" - _ "github.com/jessevdk/go-assets" - _ "github.com/manucorporat/stats" - _ "github.com/thinkerou/favicon" - _ "golang.org/x/crypto/acme/autocert" - _ "golang.org/x/lint/golint" - _ "google.golang.org/grpc" -) From 5acf6601170bb49a1958c055d66d54ba152dc34b Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Mon, 4 Feb 2019 04:27:00 +0300 Subject: [PATCH 19/23] fix travis freeze on concurrent test (#1761) --- gin_integration_test.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gin_integration_test.go b/gin_integration_test.go index 3ce6d80a..b80cbb24 100644 --- a/gin_integration_test.go +++ b/gin_integration_test.go @@ -188,15 +188,12 @@ func TestConcurrentHandleContext(t *testing.T) { }) router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) - ts := httptest.NewServer(router) - defer ts.Close() - var wg sync.WaitGroup iterations := 200 wg.Add(iterations) for i := 0; i < iterations; i++ { go func() { - testRequest(t, ts.URL+"/") + testGetRequestHandler(t, router, "/") wg.Done() }() } @@ -217,3 +214,14 @@ func TestConcurrentHandleContext(t *testing.T) { // testRequest(t, "http://localhost:8033/example") // } + +func testGetRequestHandler(t *testing.T, h http.Handler, url string) { + req, err := http.NewRequest("GET", url, nil) + assert.NoError(t, err) + + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + + assert.Equal(t, "it worked", w.Body.String(), "resp body should match") + assert.Equal(t, 200, w.Code, "should get a 200") +} From a768f064d53c8010d902533927441be13b1bfe17 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Mon, 18 Feb 2019 04:35:08 +0300 Subject: [PATCH 20/23] fix many redirects (#1760) (#1764) * fix many redirects (#1760) * fix @thinkerou review --- gin.go | 3 +++ gin_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/gin.go b/gin.go index cd47200f..6e5ea6d7 100644 --- a/gin.go +++ b/gin.go @@ -355,8 +355,11 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { // This can be done by setting c.Request.URL.Path to your new target. // Disclaimer: You can loop yourself to death with this, use wisely. func (engine *Engine) HandleContext(c *Context) { + oldIndexValue := c.index c.reset() engine.handleHTTPRequest(c) + + c.index = oldIndexValue } func (engine *Engine) handleHTTPRequest(c *Context) { diff --git a/gin_test.go b/gin_test.go index e013df09..f9a1c6f5 100644 --- a/gin_test.go +++ b/gin_test.go @@ -12,6 +12,8 @@ import ( "net/http" "net/http/httptest" "reflect" + "strconv" + "sync/atomic" "testing" "time" @@ -488,6 +490,43 @@ func TestEngineHandleContext(t *testing.T) { }) } +func TestEngineHandleContextManyReEntries(t *testing.T) { + expectValue := 10000 + + var handlerCounter, middlewareCounter int64 + + r := New() + r.Use(func(c *Context) { + atomic.AddInt64(&middlewareCounter, 1) + }) + r.GET("/:count", func(c *Context) { + countStr := c.Param("count") + count, err := strconv.Atoi(countStr) + assert.NoError(t, err) + + n, err := c.Writer.Write([]byte(".")) + assert.NoError(t, err) + assert.Equal(t, 1, n) + + switch { + case count > 0: + c.Request.URL.Path = "/" + strconv.Itoa(count-1) + r.HandleContext(c) + } + }, func(c *Context) { + atomic.AddInt64(&handlerCounter, 1) + }) + + assert.NotPanics(t, func() { + w := performRequest(r, "GET", "/"+strconv.Itoa(expectValue-1)) // include 0 value + assert.Equal(t, 200, w.Code) + assert.Equal(t, expectValue, w.Body.Len()) + }) + + assert.Equal(t, int64(expectValue), handlerCounter) + assert.Equal(t, int64(expectValue), middlewareCounter) +} + func assertRoutePresent(t *testing.T, gotRoutes RoutesInfo, wantRoute RouteInfo) { for _, gotRoute := range gotRoutes { if gotRoute.Path == wantRoute.Path && gotRoute.Method == wantRoute.Method { From 31bbb10f34e4673815ab66099571bac95cf4113d Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Mon, 18 Feb 2019 05:10:45 +0300 Subject: [PATCH 21/23] Make silent debug info on tests (#1765) * make silent log on tests * fix coverage: check end-of-line at the end of debug msg --- debug_test.go | 2 +- deprecated_test.go | 4 +++- gin_test.go | 29 +++++++++++++++++------------ githubapi_test.go | 10 +++++----- recovery_test.go | 1 + 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/debug_test.go b/debug_test.go index b3485d70..d338f0a0 100644 --- a/debug_test.go +++ b/debug_test.go @@ -39,7 +39,7 @@ func TestDebugPrint(t *testing.T) { SetMode(TestMode) debugPrint("DEBUG this!") SetMode(DebugMode) - debugPrint("these are %d %s\n", 2, "error messages") + debugPrint("these are %d %s", 2, "error messages") SetMode(TestMode) }) assert.Equal(t, "[GIN-debug] these are 2 error messages\n", re) diff --git a/deprecated_test.go b/deprecated_test.go index 7a875fe4..f8df651c 100644 --- a/deprecated_test.go +++ b/deprecated_test.go @@ -24,7 +24,9 @@ func TestBindWith(t *testing.T) { Foo string `form:"foo"` Bar string `form:"bar"` } - assert.NoError(t, c.BindWith(&obj, binding.Form)) + captureOutput(t, func() { + assert.NoError(t, c.BindWith(&obj, binding.Form)) + }) assert.Equal(t, "foo", obj.Bar) assert.Equal(t, "bar", obj.Foo) assert.Equal(t, 0, w.Body.Len()) diff --git a/gin_test.go b/gin_test.go index f9a1c6f5..11bdd79c 100644 --- a/gin_test.go +++ b/gin_test.go @@ -27,18 +27,23 @@ func formatAsDate(t time.Time) string { func setupHTMLFiles(t *testing.T, mode string, tls bool, loadMethod func(*Engine)) *httptest.Server { SetMode(mode) - router := New() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - loadMethod(router) - router.GET("/test", func(c *Context) { - c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) - }) - router.GET("/raw", func(c *Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + defer SetMode(TestMode) + + var router *Engine + captureOutput(t, func() { + router = New() + router.Delims("{[{", "}]}") + router.SetFuncMap(template.FuncMap{ + "formatAsDate": formatAsDate, + }) + loadMethod(router) + router.GET("/test", func(c *Context) { + c.HTML(http.StatusOK, "hello.tmpl", map[string]string{"name": "world"}) + }) + router.GET("/raw", func(c *Context) { + c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), + }) }) }) diff --git a/githubapi_test.go b/githubapi_test.go index 50faca09..29aa1584 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -287,7 +287,7 @@ var githubAPI = []route{ func TestShouldBindUri(t *testing.T) { DefaultWriter = os.Stdout - router := Default() + router := New() type Person struct { Name string `uri:"name" binding:"required"` @@ -309,7 +309,7 @@ func TestShouldBindUri(t *testing.T) { func TestBindUri(t *testing.T) { DefaultWriter = os.Stdout - router := Default() + router := New() type Person struct { Name string `uri:"name" binding:"required"` @@ -331,7 +331,7 @@ func TestBindUri(t *testing.T) { func TestBindUriError(t *testing.T) { DefaultWriter = os.Stdout - router := Default() + router := New() type Member struct { Number string `uri:"num" binding:"required,uuid"` @@ -361,7 +361,7 @@ func githubConfigRouter(router *Engine) { func TestGithubAPI(t *testing.T) { DefaultWriter = os.Stdout - router := Default() + router := New() githubConfigRouter(router) for _, route := range githubAPI { @@ -436,7 +436,7 @@ func BenchmarkParallelGithub(b *testing.B) { func BenchmarkParallelGithubDefault(b *testing.B) { DefaultWriter = os.Stdout - router := Default() + router := New() githubConfigRouter(router) req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil) diff --git a/recovery_test.go b/recovery_test.go index e886eaac..0a6d6271 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -43,6 +43,7 @@ func TestPanicInHandler(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Contains(t, buffer.String(), "GET /recovery") + SetMode(TestMode) } // TestPanicWithAbort assert that panic has been recovered even if context.Abort was used. From 5846ceba8b1074c4555491ef4df2a54e75442358 Mon Sep 17 00:00:00 2001 From: awkj Date: Wed, 20 Feb 2019 00:02:37 +0800 Subject: [PATCH 22/23] add notify accept signal (#1740) * add notify accept signal * add import * update readme,keep same with example --- README.md | 6 +++++- examples/graceful-shutdown/graceful-shutdown/server.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0dcaa8e9..3da8785b 100644 --- a/README.md +++ b/README.md @@ -1633,6 +1633,7 @@ import ( "net/http" "os" "os/signal" + "syscall" "time" "github.com/gin-gonic/gin" @@ -1660,7 +1661,10 @@ func main() { // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal) - signal.Notify(quit, os.Interrupt) + // kill (no param) default send syscanll.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutdown Server ...") diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go index af4f2146..33be0c8f 100644 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "os/signal" + "syscall" "time" "github.com/gin-gonic/gin" @@ -35,7 +36,10 @@ func main() { // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal) - signal.Notify(quit, os.Interrupt) + // kill (no param) default send syscanll.SIGTERM + // kill -2 is syscall.SIGINT + // kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutdown Server ...") From fece76d93fd65f795bea75f59fb5e3e03320dc6d Mon Sep 17 00:00:00 2001 From: Jeremy Loy Date: Tue, 19 Feb 2019 21:41:46 -0500 Subject: [PATCH 23/23] Add NewRelic middleware example. (#1526) * Add NewRelic middleware example. * Update go.mod * Update main.go --- examples/new_relic/README.md | 30 ++++++++++++++++++++++++++ examples/new_relic/main.go | 42 ++++++++++++++++++++++++++++++++++++ go.mod | 1 + 3 files changed, 73 insertions(+) create mode 100644 examples/new_relic/README.md create mode 100644 examples/new_relic/main.go diff --git a/examples/new_relic/README.md b/examples/new_relic/README.md new file mode 100644 index 00000000..70f14942 --- /dev/null +++ b/examples/new_relic/README.md @@ -0,0 +1,30 @@ +The [New Relic Go Agent](https://github.com/newrelic/go-agent) provides a nice middleware for the stdlib handler signature. +The following is an adaptation of that middleware for Gin. + +```golang +const ( + // NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context + NewRelicTxnKey = "NewRelicTxnKey" +) + +// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler +func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc { + return func(ctx *gin.Context) { + txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request) + defer txn.End() + ctx.Set(NewRelicTxnKey, txn) + ctx.Next() + } +} +``` +and in `main.go` or equivalent... +```golang +router := gin.Default() +cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY")) +app, err := newrelic.NewApplication(cfg) +if err != nil { + log.Printf("failed to make new_relic app: %v", err) +} else { + router.Use(adapters.NewRelicMonitoring(app)) +} + ``` diff --git a/examples/new_relic/main.go b/examples/new_relic/main.go new file mode 100644 index 00000000..f85f7831 --- /dev/null +++ b/examples/new_relic/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "log" + "net/http" + "os" + + "github.com/gin-gonic/gin" + "github.com/newrelic/go-agent" +) + +const ( + // NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context + NewRelicTxnKey = "NewRelicTxnKey" +) + +// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler +func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc { + return func(ctx *gin.Context) { + txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request) + defer txn.End() + ctx.Set(NewRelicTxnKey, txn) + ctx.Next() + } +} + +func main() { + router := gin.Default() + + cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY")) + app, err := newrelic.NewApplication(cfg) + if err != nil { + log.Printf("failed to make new_relic app: %v", err) + } else { + router.Use(NewRelicMonitoring(app)) + } + + router.GET("/", func(c *gin.Context) { + c.String(http.StatusOK, "Hello World!\n") + }) + router.Run() +} diff --git a/go.mod b/go.mod index 54573a8b..6f9d68d1 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ exclude ( github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768 github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 + github.com/newrelic/go-agent v2.5.0+incompatible github.com/thinkerou/favicon v0.1.0 golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1