From 4b5ec517daa247f8f3bd0448f82ca55c0d14fa0a Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Mon, 14 Aug 2017 11:02:31 +0800 Subject: [PATCH 001/303] fix(test): only check Location header (#1064) * fix(test): only check Location header * fix(test): rename field * fix(test): not export field --- routes_test.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/routes_test.go b/routes_test.go index 41693eed..b44b6431 100644 --- a/routes_test.go +++ b/routes_test.go @@ -356,25 +356,25 @@ func TestRouterNotFound(t *testing.T) { router.GET("/", func(c *Context) {}) testRoutes := []struct { - route string - code int - header string + route string + code int + location string }{ - {"/path/", 301, "map[Location:[/path]]"}, // TSR -/ - {"/dir", 301, "map[Location:[/dir/]]"}, // TSR +/ - {"", 301, "map[Location:[/]]"}, // TSR +/ - {"/PATH", 301, "map[Location:[/path]]"}, // Fixed Case - {"/DIR/", 301, "map[Location:[/dir/]]"}, // Fixed Case - {"/PATH/", 301, "map[Location:[/path]]"}, // Fixed Case -/ - {"/DIR", 301, "map[Location:[/dir/]]"}, // Fixed Case +/ - {"/../path", 301, "map[Location:[/path]]"}, // CleanPath - {"/nope", 404, ""}, // NotFound + {"/path/", 301, "/path"}, // TSR -/ + {"/dir", 301, "/dir/"}, // TSR +/ + {"", 301, "/"}, // TSR +/ + {"/PATH", 301, "/path"}, // Fixed Case + {"/DIR/", 301, "/dir/"}, // Fixed Case + {"/PATH/", 301, "/path"}, // Fixed Case -/ + {"/DIR", 301, "/dir/"}, // Fixed Case +/ + {"/../path", 301, "/path"}, // CleanPath + {"/nope", 404, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) assert.Equal(t, w.Code, tr.code) if w.Code != 404 { - assert.Equal(t, fmt.Sprint(w.Header()), tr.header) + assert.Equal(t, fmt.Sprint(w.Header().Get("Location")), tr.location) } } From 25d20a4463a63083b85bb2d8ebb99c7f638920f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 14 Aug 2017 12:21:05 +0800 Subject: [PATCH 002/303] merge args if it have same type (#1059) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 8347ce22..e2c0105d 100644 --- a/gin.go +++ b/gin.go @@ -268,7 +268,7 @@ func (engine *Engine) Run(addr ...string) (err error) { // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. -func (engine *Engine) RunTLS(addr string, certFile string, keyFile string) (err error) { +func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { debugPrint("Listening and serving HTTPS on %s\n", addr) defer func() { debugPrintError(err) }() From 52c2ed34b3256727a0aeac5e57129eb788bac045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 14 Aug 2017 14:34:29 +0800 Subject: [PATCH 003/303] log format (#1060) --- logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logger.go b/logger.go index 470e4bb6..7edae415 100644 --- a/logger.go +++ b/logger.go @@ -102,7 +102,7 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { path = path + "?" + raw } - fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %s %-7s %s\n%s", + fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", end.Format("2006/01/02 - 15:04:05"), statusColor, statusCode, reset, latency, From ecae34c4e186d5190ca38ac225ce03e3894d99b7 Mon Sep 17 00:00:00 2001 From: keke <19yamashita15@gmail.com> Date: Wed, 16 Aug 2017 10:50:43 +0900 Subject: [PATCH 004/303] fix 200 to http.Status (#1067) --- examples/app-engine/hello.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/app-engine/hello.go b/examples/app-engine/hello.go index da7e4ae4..f569dadb 100644 --- a/examples/app-engine/hello.go +++ b/examples/app-engine/hello.go @@ -13,10 +13,10 @@ func init() { // Define your handlers r.GET("/", func(c *gin.Context) { - c.String(200, "Hello World!") + c.String(http.StatusOK, "Hello World!") }) r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") + c.String(http.StatusOK, "pong") }) // Handle all requests using net/http From a8fa424ae529397d4a0f2a1f9fda8031851a3269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 16 Aug 2017 11:55:50 +0800 Subject: [PATCH 005/303] update comment (#1057) --- auth.go | 8 ++++---- context.go | 29 ++++++++++++++--------------- errors.go | 6 +++--- fs.go | 4 ++-- gin.go | 4 ++-- logger.go | 10 +++++----- path.go | 4 ++-- recovery.go | 2 +- render/data.go | 2 +- response_writer.go | 6 +++--- routergroup.go | 18 +++++++++--------- tree.go | 2 +- utils.go | 2 +- 13 files changed, 48 insertions(+), 49 deletions(-) diff --git a/auth.go b/auth.go index c214e213..e7c46bf6 100644 --- a/auth.go +++ b/auth.go @@ -10,10 +10,10 @@ import ( "strconv" ) -// AuthUserKey is the cookie name for user credential in basic auth +// AuthUserKey is the cookie name for user credential in basic auth. const AuthUserKey = "user" -// Accounts defines a key/value for user/pass list of authorized logins +// Accounts defines a key/value for user/pass list of authorized logins. type Accounts map[string]string type authPair struct { @@ -56,7 +56,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc { } // The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using - // c.MustGet(gin.AuthUserKey) + // c.MustGet(gin.AuthUserKey). c.Set(AuthUserKey, user) } } @@ -90,6 +90,6 @@ func secureCompare(given, actual string) bool { if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 } - // Securely compare actual to itself to keep constant time, but always return false + // Securely compare actual to itself to keep constant time, but always return false. return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false } diff --git a/context.go b/context.go index 895ba7a1..497cbfd6 100644 --- a/context.go +++ b/context.go @@ -22,7 +22,7 @@ import ( "github.com/gin-gonic/gin/render" ) -// Content-Type MIME of the most common data formats +// Content-Type MIME of the most common data formats. const ( MIMEJSON = binding.MIMEJSON MIMEHTML = binding.MIMEHTML @@ -51,13 +51,13 @@ type Context struct { engine *Engine - // Keys is a key/value pair exclusively for the context of each request + // Keys is a key/value pair exclusively for the context of each request. Keys map[string]interface{} - // Errors is a list of errors attached to all the handlers/middlewares who used this context + // Errors is a list of errors attached to all the handlers/middlewares who used this context. Errors errorMsgs - // Accepted defines a list of manually accepted formats for content negotiation + // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string } @@ -87,7 +87,7 @@ func (c *Context) Copy() *Context { } // HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", -// this function will return "main.handleGetUsers" +// this function will return "main.handleGetUsers". func (c *Context) HandlerName() string { return nameOfFunction(c.handlers.Last()) } @@ -462,18 +462,18 @@ func (c *Context) Bind(obj interface{}) error { return c.MustBindWith(obj, b) } -// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON) +// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON). func (c *Context) BindJSON(obj interface{}) error { return c.MustBindWith(obj, binding.JSON) } -// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query) +// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). func (c *Context) BindQuery(obj interface{}) error { return c.MustBindWith(obj, binding.Query) } -// MustBindWith binds the passed struct pointer using the specified binding -// engine. It will abort the request with HTTP 400 if any error ocurrs. +// MustBindWith binds the passed struct pointer using the specified binding engine. +// It will abort the request with HTTP 400 if any error ocurrs. // See the binding package. func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { if err = c.ShouldBindWith(obj, b); err != nil { @@ -483,8 +483,7 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { return } -// ShouldBindWith binds the passed struct pointer using the specified binding -// engine. +// ShouldBindWith binds the passed struct pointer using the specified binding engine. // See the binding package. func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { return b.Bind(c.Request, obj) @@ -548,7 +547,7 @@ func (c *Context) requestHeader(key string) string { /******** RESPONSE RENDERING ********/ /************************************/ -// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function +// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function. func bodyAllowedForStatus(status int) bool { switch { case status >= 100 && status <= 199: @@ -566,7 +565,7 @@ func (c *Context) Status(code int) { c.writermem.WriteHeader(code) } -// Header is a intelligent shortcut for c.Writer.Header().Set(key, value) +// Header is a intelligent shortcut for c.Writer.Header().Set(key, value). // It writes a header in the response. // If value == "", this method removes the header `c.Writer.Header().Del(key)` func (c *Context) Header(key, value string) { @@ -577,12 +576,12 @@ func (c *Context) Header(key, value string) { } } -// GetHeader returns value from request headers +// GetHeader returns value from request headers. func (c *Context) GetHeader(key string) string { return c.requestHeader(key) } -// GetRawData return stream data +// GetRawData return stream data. func (c *Context) GetRawData() ([]byte, error) { return ioutil.ReadAll(c.Request.Body) } diff --git a/errors.go b/errors.go index 19761106..6f3c9868 100644 --- a/errors.go +++ b/errors.go @@ -65,7 +65,7 @@ func (msg *Error) JSON() interface{} { return json } -// MarshalJSON implements the json.Marshaller interface +// MarshalJSON implements the json.Marshaller interface. func (msg *Error) MarshalJSON() ([]byte, error) { return json.Marshal(msg.JSON()) } @@ -80,7 +80,7 @@ func (msg *Error) IsType(flags ErrorType) bool { } // ByType returns a readonly copy filtered the byte. -// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic +// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic. func (a errorMsgs) ByType(typ ErrorType) errorMsgs { if len(a) == 0 { return nil @@ -98,7 +98,7 @@ func (a errorMsgs) ByType(typ ErrorType) errorMsgs { } // Last returns the last error in the slice. It returns nil if the array is empty. -// Shortcut for errors[len(errors)-1] +// Shortcut for errors[len(errors)-1]. func (a errorMsgs) Last() *Error { if length := len(a); length > 0 { return a[length-1] diff --git a/fs.go b/fs.go index 8570a9a9..7a6738a6 100644 --- a/fs.go +++ b/fs.go @@ -29,7 +29,7 @@ func Dir(root string, listDirectory bool) http.FileSystem { return &onlyfilesFS{fs} } -// Open conforms to http.Filesystem +// Open conforms to http.Filesystem. func (fs onlyfilesFS) Open(name string) (http.File, error) { f, err := fs.fs.Open(name) if err != nil { @@ -38,7 +38,7 @@ func (fs onlyfilesFS) Open(name string) (http.File, error) { return neuteredReaddirFile{f}, nil } -// Readdir overrides the http.File default implementation +// Readdir overrides the http.File default implementation. func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) { // this disables directory listing return nil, nil diff --git a/gin.go b/gin.go index e2c0105d..23853a97 100644 --- a/gin.go +++ b/gin.go @@ -14,7 +14,7 @@ import ( "github.com/gin-gonic/gin/render" ) -// Version is Framework's version +// Version is Framework's version. const Version = "v1.2" var default404Body = []byte("404 page not found") @@ -191,7 +191,7 @@ func (engine *Engine) NoRoute(handlers ...HandlerFunc) { engine.rebuild404Handlers() } -// NoMethod sets the handlers called when... TODO +// NoMethod sets the handlers called when... TODO. func (engine *Engine) NoMethod(handlers ...HandlerFunc) { engine.noMethod = handlers engine.rebuild405Handlers() diff --git a/logger.go b/logger.go index 7edae415..a6f7f140 100644 --- a/logger.go +++ b/logger.go @@ -25,17 +25,17 @@ var ( disableColor = false ) -// DisableConsoleColor disables color output in the console +// DisableConsoleColor disables color output in the console. func DisableConsoleColor() { disableColor = true } -// ErrorLogger returns a handlerfunc for any error type +// ErrorLogger returns a handlerfunc for any error type. func ErrorLogger() HandlerFunc { return ErrorLoggerT(ErrorTypeAny) } -// ErrorLoggerT returns a handlerfunc for a given error type +// ErrorLoggerT returns a handlerfunc for a given error type. func ErrorLoggerT(typ ErrorType) HandlerFunc { return func(c *Context) { c.Next() @@ -46,8 +46,8 @@ func ErrorLoggerT(typ ErrorType) HandlerFunc { } } -// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter -// By default gin.DefaultWriter = os.Stdout +// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. +// By default gin.DefaultWriter = os.Stdout. func Logger() HandlerFunc { return LoggerWithWriter(DefaultWriter) } diff --git a/path.go b/path.go index e3424b13..ed63ad1a 100644 --- a/path.go +++ b/path.go @@ -17,7 +17,7 @@ package gin // 4. Eliminate .. elements that begin a rooted path: // that is, replace "/.." by "/" at the beginning of a path. // -// If the result of this process is an empty string, "/" is returned +// If the result of this process is an empty string, "/" is returned. func cleanPath(p string) string { // Turn empty string into "/" if p == "" { @@ -109,7 +109,7 @@ func cleanPath(p string) string { return string(buf[:w]) } -// internal helper to lazily create a buffer if necessary +// internal helper to lazily create a buffer if necessary. func bufApp(buf *[]byte, s string, w int, c byte) { if *buf == nil { if s[w] == c { diff --git a/recovery.go b/recovery.go index c502f355..7aff3d87 100644 --- a/recovery.go +++ b/recovery.go @@ -46,7 +46,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { } } -// stack returns a nicely formated stack frame, skipping skip frames +// stack returns a nicely formated stack frame, skipping skip frames. func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data // As we loop, we open files and read them. These variables record the currently diff --git a/render/data.go b/render/data.go index c296042c..33194913 100644 --- a/render/data.go +++ b/render/data.go @@ -11,7 +11,7 @@ type Data struct { Data []byte } -// Render (Data) writes data with custom ContentType +// Render (Data) writes data with custom ContentType. func (r Data) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) _, err = w.Write(r.Data) diff --git a/response_writer.go b/response_writer.go index 216165b9..232f00aa 100644 --- a/response_writer.go +++ b/response_writer.go @@ -95,7 +95,7 @@ func (w *responseWriter) Written() bool { return w.size != noWritten } -// Hijack implements the http.Hijacker interface +// Hijack implements the http.Hijacker interface. func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { if w.size < 0 { w.size = 0 @@ -103,12 +103,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { return w.ResponseWriter.(http.Hijacker).Hijack() } -// CloseNotify implements the http.CloseNotify interface +// CloseNotify implements the http.CloseNotify interface. func (w *responseWriter) CloseNotify() <-chan bool { return w.ResponseWriter.(http.CloseNotifier).CloseNotify() } -// Flush implements the http.Flush interface +// Flush implements the http.Flush interface. func (w *responseWriter) Flush() { w.ResponseWriter.(http.Flusher).Flush() } diff --git a/routergroup.go b/routergroup.go index 89ec89aa..5e681c1c 100644 --- a/routergroup.go +++ b/routergroup.go @@ -35,7 +35,7 @@ type IRoutes interface { } // RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix -// and an array of handlers (middleware) +// and an array of handlers (middleware). type RouterGroup struct { Handlers HandlersChain basePath string @@ -89,43 +89,43 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha return group.handle(httpMethod, relativePath, handlers) } -// POST is a shortcut for router.Handle("POST", path, handle) +// POST is a shortcut for router.Handle("POST", path, handle). func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("POST", relativePath, handlers) } -// GET is a shortcut for router.Handle("GET", path, handle) +// GET is a shortcut for router.Handle("GET", path, handle). func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("GET", relativePath, handlers) } -// DELETE is a shortcut for router.Handle("DELETE", path, handle) +// DELETE is a shortcut for router.Handle("DELETE", path, handle). func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("DELETE", relativePath, handlers) } -// PATCH is a shortcut for router.Handle("PATCH", path, handle) +// PATCH is a shortcut for router.Handle("PATCH", path, handle). func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("PATCH", relativePath, handlers) } -// PUT is a shortcut for router.Handle("PUT", path, handle) +// PUT is a shortcut for router.Handle("PUT", path, handle). func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("PUT", relativePath, handlers) } -// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) +// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle). func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("OPTIONS", relativePath, handlers) } -// HEAD is a shortcut for router.Handle("HEAD", path, handle) +// HEAD is a shortcut for router.Handle("HEAD", path, handle). func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("HEAD", relativePath, handlers) } // Any registers a route that matches all the HTTP methods. -// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE +// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes { group.handle("GET", relativePath, handlers) group.handle("POST", relativePath, handlers) diff --git a/tree.go b/tree.go index 750ffae8..f67edd5d 100644 --- a/tree.go +++ b/tree.go @@ -96,7 +96,7 @@ type node struct { priority uint32 } -// increments priority of the given child and reorders if necessary +// increments priority of the given child and reorders if necessary. func (n *node) incrementChildPrio(pos int) int { n.children[pos].priority++ prio := n.children[pos].priority diff --git a/utils.go b/utils.go index 968570c7..ab06a759 100644 --- a/utils.go +++ b/utils.go @@ -47,7 +47,7 @@ func WrapH(h http.Handler) HandlerFunc { type H map[string]interface{} -// MarshalXML allows type H to be used with xml.Marshal +// MarshalXML allows type H to be used with xml.Marshal. func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error { start.Name = xml.Name{ Space: "", From 3856206bd07d1a5627eb951e345fff25f9b6209c Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Thu, 17 Aug 2017 12:18:50 +0800 Subject: [PATCH 006/303] doc(readme): add additional middleware doc. (#1056) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b14c2a2a..dc3c8667 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,7 @@ r := gin.New() instead of ```go +// Default With the Logger and Recovery middleware already attached r := gin.Default() ``` @@ -380,7 +381,10 @@ func main() { r := gin.New() // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release. By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. r.Use(gin.Recovery()) // Per route middleware, you can add as many as you desire. From f4c9ac17a49e90c14ffe08b9e50aa4ee6abf6490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 22 Aug 2017 10:27:28 +0800 Subject: [PATCH 007/303] not display color when set disableColor (#1072) --- logger.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/logger.go b/logger.go index a6f7f140..c679c787 100644 --- a/logger.go +++ b/logger.go @@ -91,10 +91,11 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { clientIP := c.ClientIP() method := c.Request.Method statusCode := c.Writer.Status() - var statusColor, methodColor string + var statusColor, methodColor, resetColor string if isTerm { statusColor = colorForStatus(statusCode) methodColor = colorForMethod(method) + resetColor = reset } comment := c.Errors.ByType(ErrorTypePrivate).String() @@ -104,10 +105,10 @@ func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { fmt.Fprintf(out, "[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", end.Format("2006/01/02 - 15:04:05"), - statusColor, statusCode, reset, + statusColor, statusCode, resetColor, latency, clientIP, - methodColor, method, reset, + methodColor, method, resetColor, path, comment, ) From 8be30bd382890156e7e813beec9f18b2c65522a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 22 Aug 2017 13:55:32 +0800 Subject: [PATCH 008/303] Update readme for showing output log to file (#1073) * Update readme for showing output log to file * update indent --- README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc3c8667..776ae8b8 100644 --- a/README.md +++ b/README.md @@ -381,7 +381,8 @@ func main() { r := gin.New() // Global middleware - // Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release. By default gin.DefaultWriter = os.Stdout + // Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger()) // Recovery middleware recovers from any panics and writes a 500 if there was one. @@ -412,6 +413,27 @@ func main() { } ``` +### Output log to file +```go +func main() { + // Disable Console Color, because not need color when output log to file + gin.DisableConsoleColor() + // Create one file to save logs + f, _ := os.Create("gin.log") + // Reset gin.DefaultWriter + gin.DefaultWriter = io.MultiWriter(f) + // If need to output log to file and console at a time, please use the following code: + // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) + + router := gin.Default() + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + r.Run(":8080") +} +``` + ### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz). From 80152ac82c413e82275e828fdde8892c458715f7 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Thu, 24 Aug 2017 10:50:31 +0800 Subject: [PATCH 009/303] doc(readme): update writing logs section wording. (#1074) * doc(readme): update writing logs section wording. * doc(readme): update writing logs section wording. * doc(readme): fix word formatting --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 776ae8b8..b5644464 100644 --- a/README.md +++ b/README.md @@ -413,16 +413,17 @@ func main() { } ``` -### Output log to file +### How to write log file ```go func main() { - // Disable Console Color, because not need color when output log to file + // Disable Console Color, you don't need console color when writing the logs to file. gin.DisableConsoleColor() - // Create one file to save logs + + // Logging to a file. f, _ := os.Create("gin.log") - // Reset gin.DefaultWriter gin.DefaultWriter = io.MultiWriter(f) - // If need to output log to file and console at a time, please use the following code: + + // Use the following code if you need to write the logs to file and console at the same time. // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) router := gin.Default() From c25254f563fc3685e22901edf2fd289902a76cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 25 Aug 2017 09:00:49 +0800 Subject: [PATCH 010/303] reduce go cyclo (#1076) * reduce go cyclo * use := var --- gin.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gin.go b/gin.go index 23853a97..ee39c9aa 100644 --- a/gin.go +++ b/gin.go @@ -316,14 +316,11 @@ func (engine *Engine) HandleContext(c *Context) { func (engine *Engine) handleHTTPRequest(context *Context) { httpMethod := context.Request.Method - var path string - var unescape bool + path := context.Request.URL.Path + unescape := false if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 { path = context.Request.URL.RawPath unescape = engine.UnescapePathValues - } else { - path = context.Request.URL.Path - unescape = false } // Find root of the tree for the given HTTP method From bc538849ebc41f2600338d9f65fad2c3008c95b2 Mon Sep 17 00:00:00 2001 From: stackerzzq Date: Fri, 25 Aug 2017 09:06:13 +0800 Subject: [PATCH 011/303] Update readme for adding tag example of binding time field (#1080) --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b5644464..879d77b4 100644 --- a/README.md +++ b/README.md @@ -532,10 +532,12 @@ package main import "log" import "github.com/gin-gonic/gin" +import "time" type Person struct { - Name string `form:"name"` - Address string `form:"address"` + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` } func main() { @@ -552,12 +554,18 @@ func startPage(c *gin.Context) { if c.Bind(&person) == nil { log.Println(person.Name) log.Println(person.Address) + log.Println(person.Birthday) } c.String(200, "Success") } ``` +Test it with: +```sh +$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" +``` + ### Bind HTML checkboxes See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) From 18b7c0892df51ba799473e57fd3d65f91c1c3aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 25 Aug 2017 09:13:53 +0800 Subject: [PATCH 012/303] not use dot when import package (#1077) --- ginS/gins.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index d40d1c3a..d238fcae 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -9,15 +9,15 @@ import ( "net/http" "sync" - . "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin" ) var once sync.Once -var internalEngine *Engine +var internalEngine *gin.Engine -func engine() *Engine { +func engine() *gin.Engine { once.Do(func() { - internalEngine = Default() + internalEngine = gin.Default() }) return internalEngine } From b9686e91fa271d2fe5da3108a2ea4087c0b0ad68 Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Fri, 25 Aug 2017 21:53:27 -0700 Subject: [PATCH 013/303] Use standard library for retrieving header (#1081) --- context.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/context.go b/context.go index 497cbfd6..6f2b0053 100644 --- a/context.go +++ b/context.go @@ -537,10 +537,7 @@ func (c *Context) IsWebsocket() bool { } func (c *Context) requestHeader(key string) string { - if values, _ := c.Request.Header[key]; len(values) > 0 { - return values[0] - } - return "" + return c.Request.Header.Get(key) } /************************************/ From fa391a4864cb2e249b4e82fd1d71261a1496f0f4 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 26 Aug 2017 03:48:56 -0500 Subject: [PATCH 014/303] chore(ci): add go 1.9 version (#1082) Signed-off-by: Bo-Yi Wu --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 821ce8df..ec12cad6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ go: - 1.6.x - 1.7.x - 1.8.x + - 1.9.x - master git: From 211c48f0408804fdbc3b698cf53243da9699c376 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 26 Aug 2017 05:02:47 -0500 Subject: [PATCH 015/303] refactor: using requestHeader internal func (#1083) * refactor: using requestHeader internal func. * update Signed-off-by: Bo-Yi Wu --- auth.go | 2 +- context.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/auth.go b/auth.go index e7c46bf6..85d96fd3 100644 --- a/auth.go +++ b/auth.go @@ -47,7 +47,7 @@ func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc { pairs := processAccounts(accounts) return func(c *Context) { // Search user in the slice of allowed credentials - user, found := pairs.searchCredential(c.Request.Header.Get("Authorization")) + user, found := pairs.searchCredential(c.requestHeader("Authorization")) if !found { // Credentials doesn't match, we return 401 and abort handlers chain. c.Header("WWW-Authenticate", realm) diff --git a/context.go b/context.go index 6f2b0053..2be7010d 100644 --- a/context.go +++ b/context.go @@ -509,7 +509,7 @@ func (c *Context) ClientIP() string { } if c.engine.AppEngine { - if addr := c.Request.Header.Get("X-Appengine-Remote-Addr"); addr != "" { + if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { return addr } } From 030b1aaf72ce020aa38b6c4bbdd6a4baf746051d Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sat, 26 Aug 2017 12:37:19 -0500 Subject: [PATCH 016/303] chore(ci): replace travis ci go 1.9.x for 1.9 (#1085) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ec12cad6..72daa3f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ go: - 1.6.x - 1.7.x - 1.8.x - - 1.9.x + - 1.9 - master git: From 26c3f42095b8a1378617eb4756fbf8282cb1ae89 Mon Sep 17 00:00:00 2001 From: Suhas Karanth Date: Sun, 27 Aug 2017 13:07:39 +0530 Subject: [PATCH 017/303] feat(binding): add support for custom validator / validation tags (#1068) * feat(binding): Add support for custom validation tags * docs: Add example for custom validation tag * test(binding): Add test for registering custom validation --- README.md | 85 ++++++++++++++++++++++++---- binding/binding.go | 11 +++- binding/default_validator.go | 5 ++ binding/validate_test.go | 42 ++++++++++++++ examples/custom-validation/server.go | 45 +++++++++++++++ 5 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 examples/custom-validation/server.go diff --git a/README.md b/README.md index 879d77b4..07357be4 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ $ go run example.go ## Benchmarks -Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) +Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) [See all benchmarks](/BENCHMARKS.md) @@ -74,10 +74,10 @@ BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848 BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609 -(1): Total Repetitions achieved in constant time, higher means more confident result -(2): Single Repetition Duration (ns/op), lower is better -(3): Heap Memory (B/op), lower is better -(4): Average Allocations per Repetition (allocs/op), lower is better +(1): Total Repetitions achieved in constant time, higher means more confident result +(2): Single Repetition Duration (ns/op), lower is better +(3): Heap Memory (B/op), lower is better +(4): Average Allocations per Repetition (allocs/op), lower is better ## Gin v1. stable @@ -281,10 +281,10 @@ func main() { // single file file, _ := c.FormFile("file") log.Println(file.Filename) - + // Upload the file to specific dst. - // c.SaveUploadedFile(file, dst) - + // c.SaveUploadedFile(file, dst) + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) }) router.Run(":8080") @@ -313,9 +313,9 @@ func main() { for _, file := range files { log.Println(file.Filename) - + // Upload the file to specific dst. - // c.SaveUploadedFile(file, dst) + // c.SaveUploadedFile(file, dst) } c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) }) @@ -487,6 +487,67 @@ func main() { } ``` +### Custom Validators + +It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go). + +[embedmd]:# (examples/custom-validation/server.go go) +```go +package main + +import ( + "net/http" + "reflect" + "time" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + validator "gopkg.in/go-playground/validator.v8" +) + +type Booking struct { + CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` + CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` +} + +func bookableDate( + v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, + field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, +) bool { + if date, ok := field.Interface().(time.Time); ok { + today := time.Now() + if today.Year() > date.Year() || today.YearDay() > date.YearDay() { + return false + } + } + return true +} + +func main() { + route := gin.Default() + binding.Validator.RegisterValidation("bookabledate", bookableDate) + route.GET("/bookable", getBookable) + route.Run(":8085") +} + +func getBookable(c *gin.Context) { + var b Booking + if err := c.ShouldBindWith(&b, binding.Query); err == nil { + c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } +} +``` + +```console +$ curl "localhost:8085/bookable?check_in=2017-08-16&check_out=2017-08-17" +{"message":"Booking dates are valid!"} + +$ curl "localhost:8085/bookable?check_in=2017-08-15&check_out=2017-08-16" +{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} +``` + ### Only Bind Query String `BindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). @@ -711,7 +772,7 @@ func main() { // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } -``` +``` ### Serving static files @@ -822,7 +883,7 @@ You may use custom delims r := gin.Default() r.Delims("{[{", "}]}") r.LoadHTMLGlob("/path/to/templates")) -``` +``` #### Custom Template Funcs diff --git a/binding/binding.go b/binding/binding.go index 971547c2..a09cc221 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -4,7 +4,11 @@ package binding -import "net/http" +import ( + "net/http" + + validator "gopkg.in/go-playground/validator.v8" +) const ( MIMEJSON = "application/json" @@ -31,6 +35,11 @@ type StructValidator interface { // If the struct is not valid or the validation itself fails, a descriptive error should be returned. // Otherwise nil must be returned. ValidateStruct(interface{}) error + + // RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key + // NOTE: if the key already exists, the previous validation function will be replaced. + // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation + RegisterValidation(string, validator.Func) error } var Validator StructValidator = &defaultValidator{} diff --git a/binding/default_validator.go b/binding/default_validator.go index 19885f16..6336bb6e 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -28,6 +28,11 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error { return nil } +func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error { + v.lazyinit() + return v.validate.RegisterValidation(key, fn) +} + func (v *defaultValidator) lazyinit() { v.once.Do(func() { config := &validator.Config{TagName: "binding"} diff --git a/binding/validate_test.go b/binding/validate_test.go index cbcb389d..523e1298 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -6,9 +6,12 @@ package binding import ( "bytes" + "reflect" "testing" "time" + validator "gopkg.in/go-playground/validator.v8" + "github.com/stretchr/testify/assert" ) @@ -190,3 +193,42 @@ func TestValidatePrimitives(t *testing.T) { assert.NoError(t, validate(&str)) assert.Equal(t, str, "value") } + +// structCustomValidation is a helper struct we use to check that +// custom validation can be registered on it. +// The `notone` binding directive is for custom validation and registered later. +type structCustomValidation struct { + Integer int `binding:"notone"` +} + +// notOne is a custom validator meant to be used with `validator.v8` library. +// The method signature for `v9` is significantly different and this function +// would need to be changed for tests to pass after upgrade. +// See https://github.com/gin-gonic/gin/pull/1015. +func notOne( + v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, + field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, +) bool { + if val, ok := field.Interface().(int); ok { + return val != 1 + } + return false +} + +func TestRegisterValidation(t *testing.T) { + // This validates that the function `notOne` matches + // the expected function signature by `defaultValidator` + // and by extension the validator library. + err := Validator.RegisterValidation("notone", notOne) + // Check that we can register custom validation without error + assert.Nil(t, err) + + // Create an instance which will fail validation + withOne := structCustomValidation{Integer: 1} + errs := validate(withOne) + + // Check that we got back non-nil errs + assert.NotNil(t, errs) + // Check that the error matches expactation + assert.Error(t, errs, "", "", "notone") +} diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go new file mode 100644 index 00000000..0b67ce10 --- /dev/null +++ b/examples/custom-validation/server.go @@ -0,0 +1,45 @@ +package main + +import ( + "net/http" + "reflect" + "time" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + validator "gopkg.in/go-playground/validator.v8" +) + +type Booking struct { + CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` + CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` +} + +func bookableDate( + v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, + field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, +) bool { + if date, ok := field.Interface().(time.Time); ok { + today := time.Now() + if today.Year() > date.Year() || today.YearDay() > date.YearDay() { + return false + } + } + return true +} + +func main() { + route := gin.Default() + binding.Validator.RegisterValidation("bookabledate", bookableDate) + route.GET("/bookable", getBookable) + route.Run(":8085") +} + +func getBookable(c *gin.Context) { + var b Booking + if err := c.ShouldBindWith(&b, binding.Query); err == nil { + c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } +} From f1edd2c2d1c0ff06ab0a3e8f3c6a3d58ee4a0a68 Mon Sep 17 00:00:00 2001 From: vz Date: Sun, 27 Aug 2017 17:11:38 +0800 Subject: [PATCH 018/303] fix(ginS): fix undefined ref (#1087) --- ginS/gins.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index d238fcae..ee00b381 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -35,65 +35,65 @@ func SetHTMLTemplate(templ *template.Template) { } // NoRoute adds handlers for NoRoute. It return a 404 code by default. -func NoRoute(handlers ...HandlerFunc) { +func NoRoute(handlers ...gin.HandlerFunc) { engine().NoRoute(handlers...) } // NoMethod sets the handlers called when... TODO -func NoMethod(handlers ...HandlerFunc) { +func NoMethod(handlers ...gin.HandlerFunc) { engine().NoMethod(handlers...) } // Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix. // For example, all the routes that use a common middlware for authorization could be grouped. -func Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { +func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup { return engine().Group(relativePath, handlers...) } -func Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes { +func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Handle(httpMethod, relativePath, handlers...) } // POST is a shortcut for router.Handle("POST", path, handle) -func POST(relativePath string, handlers ...HandlerFunc) IRoutes { +func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().POST(relativePath, handlers...) } // GET is a shortcut for router.Handle("GET", path, handle) -func GET(relativePath string, handlers ...HandlerFunc) IRoutes { +func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().GET(relativePath, handlers...) } // DELETE is a shortcut for router.Handle("DELETE", path, handle) -func DELETE(relativePath string, handlers ...HandlerFunc) IRoutes { +func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().DELETE(relativePath, handlers...) } // PATCH is a shortcut for router.Handle("PATCH", path, handle) -func PATCH(relativePath string, handlers ...HandlerFunc) IRoutes { +func PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().PATCH(relativePath, handlers...) } // PUT is a shortcut for router.Handle("PUT", path, handle) -func PUT(relativePath string, handlers ...HandlerFunc) IRoutes { +func PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().PUT(relativePath, handlers...) } // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle) -func OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes { +func OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().OPTIONS(relativePath, handlers...) } // HEAD is a shortcut for router.Handle("HEAD", path, handle) -func HEAD(relativePath string, handlers ...HandlerFunc) IRoutes { +func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().HEAD(relativePath, handlers...) } -func Any(relativePath string, handlers ...HandlerFunc) IRoutes { +func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes { return engine().Any(relativePath, handlers...) } -func StaticFile(relativePath, filepath string) IRoutes { +func StaticFile(relativePath, filepath string) gin.IRoutes { return engine().StaticFile(relativePath, filepath) } @@ -103,18 +103,18 @@ func StaticFile(relativePath, filepath string) IRoutes { // To use the operating system's file system implementation, // use : // router.Static("/static", "/var/www") -func Static(relativePath, root string) IRoutes { +func Static(relativePath, root string) gin.IRoutes { return engine().Static(relativePath, root) } -func StaticFS(relativePath string, fs http.FileSystem) IRoutes { +func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes { return engine().StaticFS(relativePath, fs) } // Use attachs a global middleware to the router. ie. the middlewares attached though Use() will be // included in the handlers chain for every single request. Even 404, 405, static files... // For example, this is the right place for a logger or error management middleware. -func Use(middlewares ...HandlerFunc) IRoutes { +func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { return engine().Use(middlewares...) } From 8902826696c1dad024e1c8ba4f903aa61a25c839 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 29 Aug 2017 03:38:53 +0800 Subject: [PATCH 019/303] doc(context): add cookie doc (#1088) --- context.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/context.go b/context.go index 2be7010d..d7b6c61d 100644 --- a/context.go +++ b/context.go @@ -583,6 +583,9 @@ func (c *Context) GetRawData() ([]byte, error) { return ioutil.ReadAll(c.Request.Body) } +// SetCookie adds a Set-Cookie header to the ResponseWriter's headers. +// The provided cookie must have a valid Name. Invalid cookies may be +// silently dropped. func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { if path == "" { path = "/" @@ -598,6 +601,10 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, }) } +// Cookie returns the named cookie provided in the request or +// ErrNoCookie if not found. And return the named cookie is unescaped. +// If multiple cookies match the given name, only one cookie will +// be returned. func (c *Context) Cookie(name string) (string, error) { cookie, err := c.Request.Cookie(name) if err != nil { From c16c2b7ec33175b23667be512a74dbb7f3943aa5 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Wed, 30 Aug 2017 12:18:57 +0800 Subject: [PATCH 020/303] chore(vendor): update jsoniter rev, #1086 (#1090) --- vendor/vendor.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index e8690a2c..bcefee10 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -34,10 +34,10 @@ "revisionTime": "2017-06-01T23:02:30Z" }, { - "checksumSHA1": "0e59uuETpidkmpaRwipQ8auqwhM=", + "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "path": "github.com/json-iterator/go", - "revision": "6b6938829d6156d7b9825f83eec757f0f571c981", - "revisionTime": "2017-07-18T14:19:52Z" + "revision": "36b14963da70d11297d313183d7e6388c8510e1e", + "revisionTime": "2017-08-29T15:58:51Z" }, { "checksumSHA1": "9if9IBLsxkarJ804NPWAzgskIAk=", From ab50cf97900b63748a62a81c06c9189ef536d121 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Fri, 1 Sep 2017 15:02:25 +0100 Subject: [PATCH 021/303] correct spelling mistake (#1092) --- recovery.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 7aff3d87..dfea8c23 100644 --- a/recovery.go +++ b/recovery.go @@ -46,7 +46,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { } } -// stack returns a nicely formated stack frame, skipping skip frames. +// stack returns a nicely formatted stack frame, skipping skip frames. func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data // As we loop, we open files and read them. These variables record the currently From cdf26f994bb551da109f4e4b8f206d8db4fdb354 Mon Sep 17 00:00:00 2001 From: George Kirilenko Date: Mon, 4 Sep 2017 04:15:50 +0300 Subject: [PATCH 022/303] 32 << 10 != 32 Mb (#1094) --- binding/form.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/binding/form.go b/binding/form.go index 557333e6..0be59660 100644 --- a/binding/form.go +++ b/binding/form.go @@ -6,6 +6,8 @@ package binding import "net/http" +const defaultMemory = 32 * 1024 * 1024 + type formBinding struct{} type formPostBinding struct{} type formMultipartBinding struct{} @@ -18,7 +20,7 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseForm(); err != nil { return err } - req.ParseMultipartForm(32 << 10) // 32 MB + req.ParseMultipartForm(defaultMemory) if err := mapForm(obj, req.Form); err != nil { return err } @@ -44,7 +46,7 @@ func (formMultipartBinding) Name() string { } func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { - if err := req.ParseMultipartForm(32 << 10); err != nil { + if err := req.ParseMultipartForm(defaultMemory); err != nil { return err } if err := mapForm(obj, req.MultipartForm.Value); err != nil { From 848fa41ca016fa3a3d385af710c4219c1cb477a4 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Tue, 5 Sep 2017 09:17:53 +0800 Subject: [PATCH 023/303] doc(recovery): add RecoveryWithWriter doc (#1097) --- recovery.go | 1 + 1 file changed, 1 insertion(+) diff --git a/recovery.go b/recovery.go index dfea8c23..89b39fec 100644 --- a/recovery.go +++ b/recovery.go @@ -26,6 +26,7 @@ func Recovery() HandlerFunc { return RecoveryWithWriter(DefaultErrorWriter) } +// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one. func RecoveryWithWriter(out io.Writer) HandlerFunc { var logger *log.Logger if out != nil { From c9b344118f6426e9211c2e4530ac983a541f437a Mon Sep 17 00:00:00 2001 From: "Daniel M. Lambea" Date: Thu, 7 Sep 2017 04:45:16 +0100 Subject: [PATCH 024/303] Moved const 'defaultMemory' to attrib. Engine.MaxMultipartMemory instead. (#1100) --- README.md | 4 ++++ context.go | 7 +++---- examples/upload-file/multiple/main.go | 2 ++ examples/upload-file/single/main.go | 2 ++ gin.go | 10 +++++++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 07357be4..d16a8682 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,8 @@ References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail ```go func main() { router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + // router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // single file file, _ := c.FormFile("file") @@ -306,6 +308,8 @@ See the detail [example code](examples/upload-file/multiple). ```go func main() { router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + // router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // Multipart form form, _ := c.MultipartForm() diff --git a/context.go b/context.go index d7b6c61d..74995e68 100644 --- a/context.go +++ b/context.go @@ -34,8 +34,7 @@ const ( ) const ( - defaultMemory = 32 << 20 // 32 MB - abortIndex int8 = math.MaxInt8 / 2 + abortIndex int8 = math.MaxInt8 / 2 ) // Context is the most important part of gin. It allows us to pass variables between middleware, @@ -407,7 +406,7 @@ func (c *Context) PostFormArray(key string) []string { func (c *Context) GetPostFormArray(key string) ([]string, bool) { req := c.Request req.ParseForm() - req.ParseMultipartForm(defaultMemory) + req.ParseMultipartForm(c.engine.MaxMultipartMemory) if values := req.PostForm[key]; len(values) > 0 { return values, true } @@ -427,7 +426,7 @@ func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { // MultipartForm is the parsed multipart form, including file uploads. func (c *Context) MultipartForm() (*multipart.Form, error) { - err := c.Request.ParseMultipartForm(defaultMemory) + err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory) return c.Request.MultipartForm, err } diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go index 4bb4cdcb..a55325ed 100644 --- a/examples/upload-file/multiple/main.go +++ b/examples/upload-file/multiple/main.go @@ -9,6 +9,8 @@ import ( func main() { router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB router.Static("/", "./public") router.POST("/upload", func(c *gin.Context) { name := c.PostForm("name") diff --git a/examples/upload-file/single/main.go b/examples/upload-file/single/main.go index 372a2994..5d438651 100644 --- a/examples/upload-file/single/main.go +++ b/examples/upload-file/single/main.go @@ -9,6 +9,8 @@ import ( func main() { router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + router.MaxMultipartMemory = 8 << 20 // 8 MiB router.Static("/", "./public") router.POST("/upload", func(c *gin.Context) { name := c.PostForm("name") diff --git a/gin.go b/gin.go index ee39c9aa..24c4dd8d 100644 --- a/gin.go +++ b/gin.go @@ -15,7 +15,10 @@ import ( ) // Version is Framework's version. -const Version = "v1.2" +const ( + Version = "v1.2" + defaultMultipartMemory = 32 << 20 // 32 MB +) var default404Body = []byte("404 page not found") var default405Body = []byte("405 method not allowed") @@ -92,6 +95,10 @@ type Engine struct { // If UseRawPath is false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. UnescapePathValues bool + + // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm + // method call. + MaxMultipartMemory int64 } var _ IRouter = &Engine{} @@ -120,6 +127,7 @@ func New() *Engine { AppEngine: defaultAppEngine, UseRawPath: false, UnescapePathValues: true, + MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", From b1ee49de8c80ea72acf019315d3dffc0ce6e06ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 8 Sep 2017 08:56:56 +0800 Subject: [PATCH 025/303] fix typo (#1103) --- deprecated.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deprecated.go b/deprecated.go index 7b50dc70..ab447429 100644 --- a/deprecated.go +++ b/deprecated.go @@ -15,7 +15,7 @@ import ( func (c *Context) BindWith(obj interface{}, b binding.Binding) error { log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to be deprecated, please check issue #662 and either use MustBindWith() if you - want HTTP 400 to be automatically returned if any error occur, of use + want HTTP 400 to be automatically returned if any error occur, or use ShouldBindWith() if you need to manage the error.`) return c.MustBindWith(obj, b) } From 8c17c680d9e4a6a6d7c2fd48eafe463eac23372e Mon Sep 17 00:00:00 2001 From: "Kristoffer A. Iversen" Date: Mon, 11 Sep 2017 16:17:26 +0200 Subject: [PATCH 026/303] Fixed README.md typo (#1104) * Fixed README.md typo * Fixed example typo * Fixed example typo --- README.md | 2 +- examples/graceful-shutdown/close/server.go | 2 +- examples/graceful-shutdown/graceful-shutdown/server.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d16a8682..0dc165bc 100644 --- a/README.md +++ b/README.md @@ -1224,7 +1224,7 @@ func main() { if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } - log.Println("Server exist") + log.Println("Server exiting") } ``` diff --git a/examples/graceful-shutdown/close/server.go b/examples/graceful-shutdown/close/server.go index 54778393..9c4e90fa 100644 --- a/examples/graceful-shutdown/close/server.go +++ b/examples/graceful-shutdown/close/server.go @@ -41,5 +41,5 @@ func main() { } } - log.Println("Server exist") + log.Println("Server exiting") } diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go index 060de081..6debe7f5 100644 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -44,5 +44,5 @@ func main() { if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } - log.Println("Server exist") + log.Println("Server exiting") } From 5afc5b19730118c9b8324fe9dd995d44ec65c81a Mon Sep 17 00:00:00 2001 From: Davor Kapsa Date: Mon, 11 Sep 2017 16:33:19 +0200 Subject: [PATCH 027/303] travis: add 1.9.x instead 1.9 to go version (#1105) 1.9.x should be working now. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 72daa3f3..ec12cad6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ go: - 1.6.x - 1.7.x - 1.8.x - - 1.9 + - 1.9.x - master git: From a8c53949e5b7d8a00a9e26b1609a7626e7d0b127 Mon Sep 17 00:00:00 2001 From: delphinus Date: Thu, 28 Sep 2017 23:23:18 +0900 Subject: [PATCH 028/303] Support time location on form binding (#1117) --- binding/form_mapping.go | 8 ++++++++ context_test.go | 21 +++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 34f12678..c968dc08 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -163,6 +163,14 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val l = time.UTC } + if locTag := structField.Tag.Get("time_location"); locTag != "" { + loc, err := time.LoadLocation(locTag) + if err != nil { + return err + } + l = loc + } + t, err := time.ParseInLocation(timeFormat, val, l) if err != nil { return err diff --git a/context_test.go b/context_test.go index 15569bf2..4854a8a8 100644 --- a/context_test.go +++ b/context_test.go @@ -45,6 +45,7 @@ func createMultipartRequest() *http.Request { must(mw.WriteField("id", "")) must(mw.WriteField("time_local", "31/12/2016 14:55")) must(mw.WriteField("time_utc", "31/12/2016 14:55")) + must(mw.WriteField("time_location", "31/12/2016 14:55")) req, err := http.NewRequest("POST", "/", body) must(err) req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) @@ -444,14 +445,15 @@ func TestContextPostFormMultipart(t *testing.T) { c.Request = createMultipartRequest() var obj struct { - Foo string `form:"foo"` - Bar string `form:"bar"` - BarAsInt int `form:"bar"` - Array []string `form:"array"` - ID string `form:"id"` - TimeLocal time.Time `form:"time_local" time_format:"02/01/2006 15:04"` - TimeUTC time.Time `form:"time_utc" time_format:"02/01/2006 15:04" time_utc:"1"` - BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"` + Foo string `form:"foo"` + Bar string `form:"bar"` + BarAsInt int `form:"bar"` + Array []string `form:"array"` + ID string `form:"id"` + TimeLocal time.Time `form:"time_local" time_format:"02/01/2006 15:04"` + TimeUTC time.Time `form:"time_utc" time_format:"02/01/2006 15:04" time_utc:"1"` + TimeLocation time.Time `form:"time_location" time_format:"02/01/2006 15:04" time_location:"Asia/Tokyo"` + BlankTime time.Time `form:"blank_time" time_format:"02/01/2006 15:04"` } assert.NoError(t, c.Bind(&obj)) assert.Equal(t, obj.Foo, "bar") @@ -463,6 +465,9 @@ func TestContextPostFormMultipart(t *testing.T) { assert.Equal(t, obj.TimeLocal.Location(), time.Local) assert.Equal(t, obj.TimeUTC.Format("02/01/2006 15:04"), "31/12/2016 14:55") assert.Equal(t, obj.TimeUTC.Location(), time.UTC) + loc, _ := time.LoadLocation("Asia/Tokyo") + assert.Equal(t, obj.TimeLocation.Format("02/01/2006 15:04"), "31/12/2016 14:55") + assert.Equal(t, obj.TimeLocation.Location(), loc) assert.True(t, obj.BlankTime.IsZero()) value, ok := c.GetQuery("foo") From f7376f7c7fc73e2fdd30623f413f87a32e73baa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 28 Sep 2017 09:54:37 -0500 Subject: [PATCH 029/303] combine var and use tmp var (#1108) --- gin.go | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/gin.go b/gin.go index 24c4dd8d..3a51bc14 100644 --- a/gin.go +++ b/gin.go @@ -20,9 +20,11 @@ const ( defaultMultipartMemory = 32 << 20 // 32 MB ) -var default404Body = []byte("404 page not found") -var default405Body = []byte("405 method not allowed") -var defaultAppEngine bool +var ( + default404Body = []byte("404 page not found") + default405Body = []byte("405 method not allowed") + defaultAppEngine bool +) type HandlerFunc func(*Context) type HandlersChain []HandlerFunc @@ -91,6 +93,7 @@ type Engine struct { // If enabled, the url.RawPath will be used to find parameters. UseRawPath bool + // If true, the path value will be unescaped. // If UseRawPath is false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. @@ -161,13 +164,16 @@ func (engine *Engine) SecureJsonPrefix(prefix string) *Engine { } func (engine *Engine) LoadHTMLGlob(pattern string) { + left := engine.delims.Left + right := engine.delims.Right + if IsDebugging() { - debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern))) + debugPrintLoadTemplate(template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))) engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} return } - templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)) + templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) engine.SetHTMLTemplate(templ) } @@ -322,12 +328,12 @@ func (engine *Engine) HandleContext(c *Context) { engine.pool.Put(c) } -func (engine *Engine) handleHTTPRequest(context *Context) { - httpMethod := context.Request.Method - path := context.Request.URL.Path +func (engine *Engine) handleHTTPRequest(c *Context) { + httpMethod := c.Request.Method + path := c.Request.URL.Path unescape := false - if engine.UseRawPath && len(context.Request.URL.RawPath) > 0 { - path = context.Request.URL.RawPath + if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 { + path = c.Request.URL.RawPath unescape = engine.UnescapePathValues } @@ -337,20 +343,20 @@ func (engine *Engine) handleHTTPRequest(context *Context) { if t[i].method == httpMethod { root := t[i].root // Find route in tree - handlers, params, tsr := root.getValue(path, context.Params, unescape) + handlers, params, tsr := root.getValue(path, c.Params, unescape) if handlers != nil { - context.handlers = handlers - context.Params = params - context.Next() - context.writermem.WriteHeaderNow() + c.handlers = handlers + c.Params = params + c.Next() + c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && path != "/" { if tsr && engine.RedirectTrailingSlash { - redirectTrailingSlash(context) + redirectTrailingSlash(c) return } - if engine.RedirectFixedPath && redirectFixedPath(context, root, engine.RedirectFixedPath) { + if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) { return } } @@ -362,15 +368,15 @@ func (engine *Engine) handleHTTPRequest(context *Context) { for _, tree := range engine.trees { if tree.method != httpMethod { if handlers, _, _ := tree.root.getValue(path, nil, unescape); handlers != nil { - context.handlers = engine.allNoMethod - serveError(context, 405, default405Body) + c.handlers = engine.allNoMethod + serveError(c, 405, default405Body) return } } } } - context.handlers = engine.allNoRoute - serveError(context, 404, default404Body) + c.handlers = engine.allNoRoute + serveError(c, 404, default404Body) } var mimePlain = []string{MIMEPlain} From 3b300929e80cede939bd5f0016b6692d20491ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 28 Sep 2017 11:22:35 -0500 Subject: [PATCH 030/303] Empty string check (#1101) --- auth.go | 4 ++-- gin.go | 2 +- mode.go | 2 +- render/html.go | 4 ++-- utils.go | 11 +++++------ 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/auth.go b/auth.go index 85d96fd3..302a4fad 100644 --- a/auth.go +++ b/auth.go @@ -24,7 +24,7 @@ type authPair struct { type authPairs []authPair func (a authPairs) searchCredential(authValue string) (string, bool) { - if len(authValue) == 0 { + if authValue == "" { return "", false } for _, pair := range a { @@ -71,7 +71,7 @@ func processAccounts(accounts Accounts) authPairs { assert1(len(accounts) > 0, "Empty list of authorized credentials") pairs := make(authPairs, 0, len(accounts)) for user, password := range accounts { - assert1(len(user) > 0, "User can not be empty") + assert1(user != "", "User can not be empty") value := authorizationHeader(user, password) pairs = append(pairs, authPair{ Value: value, diff --git a/gin.go b/gin.go index 3a51bc14..9466e074 100644 --- a/gin.go +++ b/gin.go @@ -231,7 +231,7 @@ func (engine *Engine) rebuild405Handlers() { func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { assert1(path[0] == '/', "path must begin with '/'") - assert1(len(method) > 0, "HTTP method can not be empty") + assert1(method != "", "HTTP method can not be empty") assert1(len(handlers) > 0, "there must be at least one handler") debugPrintRoute(method, path, handlers) diff --git a/mode.go b/mode.go index b0d2c27d..393e13ec 100644 --- a/mode.go +++ b/mode.go @@ -39,7 +39,7 @@ var modeName = DebugMode func init() { mode := os.Getenv(ENV_GIN_MODE) - if len(mode) == 0 { + if mode == "" { SetMode(DebugMode) } else { SetMode(mode) diff --git a/render/html.go b/render/html.go index 332d3ba2..1e3be65b 100644 --- a/render/html.go +++ b/render/html.go @@ -60,7 +60,7 @@ func (r HTMLDebug) loadTemplate() *template.Template { if len(r.Files) > 0 { return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...)) } - if len(r.Glob) > 0 { + if r.Glob != "" { return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob)) } panic("the HTML debug render was created without files or glob pattern") @@ -69,7 +69,7 @@ func (r HTMLDebug) loadTemplate() *template.Template { func (r HTML) Render(w http.ResponseWriter) error { r.WriteContentType(w) - if len(r.Name) == 0 { + if r.Name == "" { return r.Template.Execute(w, r.Data) } return r.Template.ExecuteTemplate(w, r.Name, r.Data) diff --git a/utils.go b/utils.go index ab06a759..e909bb70 100644 --- a/utils.go +++ b/utils.go @@ -103,7 +103,7 @@ func parseAccept(acceptHeader string) []string { if index := strings.IndexByte(part, ';'); index >= 0 { part = part[0:index] } - if part = strings.TrimSpace(part); len(part) > 0 { + if part = strings.TrimSpace(part); part != "" { out = append(out, part) } } @@ -111,11 +111,10 @@ func parseAccept(acceptHeader string) []string { } func lastChar(str string) uint8 { - size := len(str) - if size == 0 { + if str == "" { panic("The length of the string can't be 0") } - return str[size-1] + return str[len(str)-1] } func nameOfFunction(f interface{}) string { @@ -123,7 +122,7 @@ func nameOfFunction(f interface{}) string { } func joinPaths(absolutePath, relativePath string) string { - if len(relativePath) == 0 { + if relativePath == "" { return absolutePath } @@ -138,7 +137,7 @@ func joinPaths(absolutePath, relativePath string) string { func resolveAddress(addr []string) string { switch len(addr) { case 0: - if port := os.Getenv("PORT"); len(port) > 0 { + if port := os.Getenv("PORT"); port != "" { debugPrint("Environment variable PORT=\"%s\"", port) return ":" + port } From 0cb7c44abc8280411ee5ab3cc67ff63aaee2bf3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 28 Sep 2017 22:58:57 -0500 Subject: [PATCH 031/303] Print warning log when user create one engine using gin.Default in debug mode (#1121) * empty string check * add log when use Default * fix unit test error * fix unit test error --- debug.go | 6 ++++++ debug_test.go | 18 ++++++++++++++++++ gin.go | 1 + 3 files changed, 25 insertions(+) diff --git a/debug.go b/debug.go index b31ca685..449291e6 100644 --- a/debug.go +++ b/debug.go @@ -46,6 +46,12 @@ func debugPrint(format string, values ...interface{}) { } } +func debugPrintWARNINGDefault() { + debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. + +`) +} + func debugPrintWARNINGNew() { debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release diff --git a/debug_test.go b/debug_test.go index 366d4613..dfd54c82 100644 --- a/debug_test.go +++ b/debug_test.go @@ -86,6 +86,24 @@ func TestDebugPrintWARNINGSetHTMLTemplate(t *testing.T) { assert.Equal(t, "[GIN-debug] [WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called\nat initialization. ie. before any route is registered or the router is listening in a socket:\n\n\trouter := gin.Default()\n\trouter.SetHTMLTemplate(template) // << good place\n\n", w.String()) } +func TestDebugPrintWARNINGDefault(t *testing.T) { + var w bytes.Buffer + setup(&w) + defer teardown() + + debugPrintWARNINGDefault() + assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", w.String()) +} + +func TestDebugPrintWARNINGNew(t *testing.T) { + var w bytes.Buffer + setup(&w) + defer teardown() + + debugPrintWARNINGNew() + assert.Equal(t, "[GIN-debug] [WARNING] Running in \"debug\" mode. Switch to \"release\" mode in production.\n - using env:\texport GIN_MODE=release\n - using code:\tgin.SetMode(gin.ReleaseMode)\n\n", w.String()) +} + func setup(w io.Writer) { SetMode(DebugMode) log.SetOutput(w) diff --git a/gin.go b/gin.go index 9466e074..8cfb1f13 100644 --- a/gin.go +++ b/gin.go @@ -144,6 +144,7 @@ func New() *Engine { // Default returns an Engine instance with the Logger and Recovery middleware already attached. func Default() *Engine { + debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) return engine From b8b68314faa067acae5b17c828127b04af51ebe2 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Fri, 29 Sep 2017 16:48:10 +0800 Subject: [PATCH 032/303] feat: add multiple service example. (#1119) --- README.md | 82 +++++++++++++++++++++++++++++++ examples/multiple-service/main.go | 74 ++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 examples/multiple-service/main.go diff --git a/README.md b/README.md index 0dc165bc..66c83fb1 100644 --- a/README.md +++ b/README.md @@ -1154,6 +1154,88 @@ func main() { } ``` +### Run multiple service using Gin + +See the [question](https://github.com/gin-gonic/gin/issues/346) and try the folling example: + +[embedmd]:# (examples/multiple-service/main.go go) +```go +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" +) + +var ( + g errgroup.Group +) + +func router01() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 01", + }, + ) + }) + + return e +} + +func router02() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 02", + }, + ) + }) + + return e +} + +func main() { + server01 := &http.Server{ + Addr: ":8080", + Handler: router01(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + server02 := &http.Server{ + Addr: ":8081", + Handler: router02(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + g.Go(func() error { + return server01.ListenAndServe() + }) + + g.Go(func() error { + return server02.ListenAndServe() + }) + + if err := g.Wait(); err != nil { + log.Fatal(err) + } +} +``` + ### Graceful restart or stop Do you want to graceful restart or stop your web server? diff --git a/examples/multiple-service/main.go b/examples/multiple-service/main.go new file mode 100644 index 00000000..ceddaa2e --- /dev/null +++ b/examples/multiple-service/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "log" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" +) + +var ( + g errgroup.Group +) + +func router01() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 01", + }, + ) + }) + + return e +} + +func router02() http.Handler { + e := gin.New() + e.Use(gin.Recovery()) + e.GET("/", func(c *gin.Context) { + c.JSON( + http.StatusOK, + gin.H{ + "code": http.StatusOK, + "error": "Welcome server 02", + }, + ) + }) + + return e +} + +func main() { + server01 := &http.Server{ + Addr: ":8080", + Handler: router01(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + server02 := &http.Server{ + Addr: ":8081", + Handler: router02(), + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + } + + g.Go(func() error { + return server01.ListenAndServe() + }) + + g.Go(func() error { + return server02.ListenAndServe() + }) + + if err := g.Wait(); err != nil { + log.Fatal(err) + } +} From dfb68ce0852d3fe600148f486f67d2327146dfea Mon Sep 17 00:00:00 2001 From: Suhas Karanth Date: Mon, 23 Oct 2017 14:44:09 +0530 Subject: [PATCH 033/303] feat(context): ShouldBind counterparts for Bind methods (#1047) * feat(context): ShouldBind counterparts for Bind methods + tests * docs(readme): Switch examples to use ShouldBind methods Add section for bind methods types, explain difference in behavior. Switch all `c.Bind` examples to use `c.ShouldBind`. --- README.md | 62 ++++++++++++++++++++++++++++++++++----------- context.go | 23 +++++++++++++++++ context_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 66c83fb1..7113058d 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ func main() { // Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release. // By default gin.DefaultWriter = os.Stdout r.Use(gin.Logger()) - + // Recovery middleware recovers from any panics and writes a 500 if there was one. r.Use(gin.Recovery()) @@ -422,11 +422,11 @@ func main() { func main() { // Disable Console Color, you don't need console color when writing the logs to file. gin.DisableConsoleColor() - + // Logging to a file. f, _ := os.Create("gin.log") gin.DefaultWriter = io.MultiWriter(f) - + // Use the following code if you need to write the logs to file and console at the same time. // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) @@ -447,9 +447,17 @@ Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/valid Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. -When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use BindWith. +Also, Gin provides two sets of methods for binding: +- **Type** - Must bind + - **Methods** - `Bind`, `BindJSON`, `BindQuery` + - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. +- **Type** - Should bind + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery` + - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. -You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, the current request will fail with an error. +When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. + +You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned. ```go // Binding from JSON @@ -464,12 +472,14 @@ func main() { // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login - if c.BindJSON(&json) == nil { + if err = c.ShouldBindJSON(&json); err == nil { if json.User == "manu" && json.Password == "123" { c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) } + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) @@ -477,12 +487,14 @@ func main() { router.POST("/loginForm", func(c *gin.Context) { var form Login // This will infer what binder to use depending on the content-type header. - if c.Bind(&form) == nil { + if err := c.ShouldBind(&form); err == nil { if form.User == "manu" && form.Password == "123" { c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) } + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) @@ -491,6 +503,28 @@ func main() { } ``` +**Sample request** +```shell +$ curl -v -X POST \ + http://localhost:8080/loginJSON \ + -H 'content-type: application/json' \ + -d '{ "user": "manu" }' +> POST /loginJSON HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.51.0 +> Accept: */* +> content-type: application/json +> Content-Length: 18 +> +* upload completely sent off: 18 out of 18 bytes +< HTTP/1.1 400 Bad Request +< Content-Type: application/json; charset=utf-8 +< Date: Fri, 04 Aug 2017 03:51:31 GMT +< Content-Length: 100 +< +{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} +``` + ### Custom Validators It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go). @@ -554,7 +588,7 @@ $ curl "localhost:8085/bookable?check_in=2017-08-15&check_out=2017-08-16" ### Only Bind Query String -`BindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). +`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). ```go package main @@ -578,7 +612,7 @@ func main() { func startPage(c *gin.Context) { var person Person - if c.BindQuery(&person) == nil { + if c.ShouldBindQuery(&person) == nil { log.Println("====== Only Bind By Query String ======") log.Println(person.Name) log.Println(person.Address) @@ -616,7 +650,7 @@ func startPage(c *gin.Context) { // If `GET`, only `Form` binding engine (`query`) used. // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 - if c.Bind(&person) == nil { + if c.ShouldBind(&person) == nil { log.Println(person.Name) log.Println(person.Address) log.Println(person.Birthday) @@ -648,7 +682,7 @@ type myForm struct { func formHandler(c *gin.Context) { var fakeForm myForm - c.Bind(&fakeForm) + c.ShouldBind(&fakeForm) c.JSON(200, gin.H{"color": fakeForm.Colors}) } @@ -695,11 +729,11 @@ func main() { router := gin.Default() router.POST("/login", func(c *gin.Context) { // you can bind multipart form with explicit binding declaration: - // c.MustBindWith(&form, binding.Form) - // or you can simply use autobinding with Bind method: + // c.ShouldBindWith(&form, binding.Form) + // or you can simply use autobinding with ShouldBind method: var form LoginForm // in this case proper binding will be automatically selected - if c.Bind(&form) == nil { + if c.ShouldBind(&form) == nil { if form.User == "user" && form.Password == "password" { c.JSON(200, gin.H{"status": "you are logged in"}) } else { diff --git a/context.go b/context.go index 74995e68..5b67dcc0 100644 --- a/context.go +++ b/context.go @@ -482,6 +482,29 @@ func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { return } +// ShouldBind checks the Content-Type to select a binding engine automatically, +// Depending the "Content-Type" header different bindings are used: +// "application/json" --> JSON binding +// "application/xml" --> XML binding +// otherwise --> returns an error +// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. +// It decodes the json payload into the struct specified as a pointer. +// Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid. +func (c *Context) ShouldBind(obj interface{}) error { + b := binding.Default(c.Request.Method, c.ContentType()) + return c.ShouldBindWith(obj, b) +} + +// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). +func (c *Context) ShouldBindJSON(obj interface{}) error { + return c.ShouldBindWith(obj, binding.JSON) +} + +// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). +func (c *Context) ShouldBindQuery(obj interface{}) error { + return c.ShouldBindWith(obj, binding.Query) +} + // ShouldBindWith binds the passed struct pointer using the specified binding engine. // See the binding package. func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { diff --git a/context_test.go b/context_test.go index 4854a8a8..8cd05f6c 100644 --- a/context_test.go +++ b/context_test.go @@ -1228,6 +1228,73 @@ func TestContextBadAutoBind(t *testing.T) { assert.True(t, c.IsAborted()) } +func TestContextAutoShouldBindJSON(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request.Header.Add("Content-Type", MIMEJSON) + + var obj struct { + Foo string `json:"foo"` + Bar string `json:"bar"` + } + assert.NoError(t, c.ShouldBind(&obj)) + assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, obj.Foo, "bar") + assert.Empty(t, c.Errors) +} + +func TestContextShouldBindWithJSON(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type + + var obj struct { + Foo string `json:"foo"` + Bar string `json:"bar"` + } + assert.NoError(t, c.ShouldBindJSON(&obj)) + assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, w.Body.Len(), 0) +} + +func TestContextShouldBindWithQuery(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/?foo=bar&bar=foo", bytes.NewBufferString("foo=unused")) + + var obj struct { + Foo string `form:"foo"` + Bar string `form:"bar"` + } + assert.NoError(t, c.ShouldBindQuery(&obj)) + assert.Equal(t, "foo", obj.Bar) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, 0, w.Body.Len()) +} + +func TestContextBadAutoShouldBind(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) + c.Request.Header.Add("Content-Type", MIMEJSON) + var obj struct { + Foo string `json:"foo"` + Bar string `json:"bar"` + } + + assert.False(t, c.IsAborted()) + assert.Error(t, c.ShouldBind(&obj)) + + assert.Empty(t, obj.Bar) + assert.Empty(t, obj.Foo) + assert.False(t, c.IsAborted()) +} + func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) From 1e88466d234a82ce4aeca904bce8a0b93fac3d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 26 Oct 2017 10:25:25 +0800 Subject: [PATCH 034/303] Update the comment of Version (#1141) --- gin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gin.go b/gin.go index 8cfb1f13..4857f3ae 100644 --- a/gin.go +++ b/gin.go @@ -14,8 +14,8 @@ import ( "github.com/gin-gonic/gin/render" ) -// Version is Framework's version. const ( + // Version is Framework's version. Version = "v1.2" defaultMultipartMemory = 32 << 20 // 32 MB ) From b7e8a6b9b062473c7f3f4f5c16d0a28b6244de48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 29 Oct 2017 20:12:22 +0800 Subject: [PATCH 035/303] style(import): not use aliase when import package (#1146) --- binding/binding.go | 2 +- binding/json.go | 4 +--- binding/query.go | 4 +--- binding/validate_test.go | 3 +-- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index a09cc221..dc32d538 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -7,7 +7,7 @@ package binding import ( "net/http" - validator "gopkg.in/go-playground/validator.v8" + "gopkg.in/go-playground/validator.v8" ) const ( diff --git a/binding/json.go b/binding/json.go index f600a5f4..b7c856af 100644 --- a/binding/json.go +++ b/binding/json.go @@ -10,9 +10,7 @@ import ( "github.com/gin-gonic/gin/json" ) -var ( - EnableDecoderUseNumber = false -) +var EnableDecoderUseNumber = false type jsonBinding struct{} diff --git a/binding/query.go b/binding/query.go index a789f798..219743f2 100644 --- a/binding/query.go +++ b/binding/query.go @@ -4,9 +4,7 @@ package binding -import ( - "net/http" -) +import "net/http" type queryBinding struct{} diff --git a/binding/validate_test.go b/binding/validate_test.go index 523e1298..8ca79989 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -10,9 +10,8 @@ import ( "testing" "time" - validator "gopkg.in/go-playground/validator.v8" - "github.com/stretchr/testify/assert" + "gopkg.in/go-playground/validator.v8" ) type testInterface interface { From 6653d5d588740eb73d2245ef04d5a407d3c3f034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 30 Oct 2017 10:38:38 +0800 Subject: [PATCH 036/303] fix markdown render (#1149) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7113058d..b7181036 100644 --- a/README.md +++ b/README.md @@ -74,10 +74,10 @@ BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848 BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609 -(1): Total Repetitions achieved in constant time, higher means more confident result -(2): Single Repetition Duration (ns/op), lower is better -(3): Heap Memory (B/op), lower is better -(4): Average Allocations per Repetition (allocs/op), lower is better +- (1): Total Repetitions achieved in constant time, higher means more confident result +- (2): Single Repetition Duration (ns/op), lower is better +- (3): Heap Memory (B/op), lower is better +- (4): Average Allocations per Repetition (allocs/op), lower is better ## Gin v1. stable From 76ad15ab32d000115cb7622a8317d7425dfc039c Mon Sep 17 00:00:00 2001 From: Mike Stipicevic Date: Thu, 2 Nov 2017 14:48:54 +0100 Subject: [PATCH 037/303] Update comment to reflect correct constant. (#1144) The constant is `DebugMode`, as defined in [mode.go](https://github.com/gin-gonic/gin/blob/1e88466d234a82ce4aeca904bce8a0b93fac3d42/mode.go#L16). --- debug.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug.go b/debug.go index 449291e6..897c4943 100644 --- a/debug.go +++ b/debug.go @@ -15,7 +15,7 @@ func init() { } // IsDebugging returns true if the framework is running in debug mode. -// Use SetMode(gin.Release) to disable debug mode. +// Use SetMode(gin.ReleaseMode) to disable debug mode. func IsDebugging() bool { return ginMode == debugCode } From 9ae1e5db2a47219bfae9147d372bd07b21ca0e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Nov 2017 13:11:22 +0800 Subject: [PATCH 038/303] add package for govendor (#1166) * add package for govendor * fix vet error. Signed-off-by: Bo-Yi Wu * add missing example. Signed-off-by: Bo-Yi Wu * update gin-gonic/autotls Signed-off-by: Bo-Yi Wu --- README.md | 4 ++-- .../auto-tls/{example1.go => example1/main.go} | 0 .../auto-tls/{example2.go => example2/main.go} | 0 vendor/vendor.json | 18 +++++++++++++++--- 4 files changed, 17 insertions(+), 5 deletions(-) rename examples/auto-tls/{example1.go => example1/main.go} (100%) rename examples/auto-tls/{example2.go => example2/main.go} (100%) diff --git a/README.md b/README.md index b7181036..6b8cdd7e 100644 --- a/README.md +++ b/README.md @@ -1133,7 +1133,7 @@ func main() { example for 1-line LetsEncrypt HTTPS servers. -[embedmd]:# (examples/auto-tls/example1.go go) +[embedmd]:# (examples/auto-tls/example1/main.go go) ```go package main @@ -1158,7 +1158,7 @@ func main() { example for custom autocert manager. -[embedmd]:# (examples/auto-tls/example2.go go) +[embedmd]:# (examples/auto-tls/example2/main.go go) ```go package main diff --git a/examples/auto-tls/example1.go b/examples/auto-tls/example1/main.go similarity index 100% rename from examples/auto-tls/example1.go rename to examples/auto-tls/example1/main.go diff --git a/examples/auto-tls/example2.go b/examples/auto-tls/example2/main.go similarity index 100% rename from examples/auto-tls/example2.go rename to examples/auto-tls/example2/main.go diff --git a/vendor/vendor.json b/vendor/vendor.json index bcefee10..573abe24 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -22,10 +22,10 @@ "revisionTime": "2017-01-09T09:34:21Z" }, { - "checksumSHA1": "FJKrZuFmeLJp8HDeJc6UkIDBPUw=", + "checksumSHA1": "+vZNyF2MykVjenLg1TpjjgjthV0=", "path": "github.com/gin-gonic/autotls", - "revision": "5b3297bdcee778ff3bbdc99ab7c41e1c2677d22d", - "revisionTime": "2017-04-16T09:39:34Z" + "revision": "8ca25fbde72bb72a00466215b94b489c71fcb815", + "revisionTime": "2017-09-16T16:54:15Z" }, { "checksumSHA1": "qlPUeFabwF4RKAOF1H+yBFU1Veg=", @@ -65,6 +65,12 @@ "revision": "976c720a22c8eb4eb6a0b4348ad85ad12491a506", "revisionTime": "2016-09-25T22:06:09Z" }, + { + "checksumSHA1": "IopMW+arBezL5bqOfrVU6UEfn28=", + "path": "github.com/thinkerou/favicon", + "revision": "94a442a49da6e2d44bdd5e0d2e2e185c43a19d93", + "revisionTime": "2017-07-10T14:05:20Z" + }, { "checksumSHA1": "CoxdaTYdPZNJXr8mJfLxye428N0=", "path": "github.com/ugorji/go/codec", @@ -90,6 +96,12 @@ "revision": "d4c55e66d8c3a2f3382d264b08e3e3454a66355a", "revisionTime": "2016-10-18T08:54:36Z" }, + { + "checksumSHA1": "S0DP7Pn7sZUmXc55IzZnNvERu6s=", + "path": "golang.org/x/sync/errgroup", + "revision": "8e0aa688b654ef28caa72506fa5ec8dba9fc7690", + "revisionTime": "2017-07-19T03:38:01Z" + }, { "checksumSHA1": "TVEkpH3gq84iQ39I4R+mlDwjuVI=", "path": "golang.org/x/sys/unix", From 9a4ecc87d6f8272b8e2450f9c0ab12d3e814521f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 Nov 2017 13:24:51 +0800 Subject: [PATCH 039/303] format some codes style (#1165) --- context.go | 13 +++++-------- gin.go | 4 ++-- json/json.go | 4 +--- json/jsoniter.go | 4 +--- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/context.go b/context.go index 5b67dcc0..1ed65fed 100644 --- a/context.go +++ b/context.go @@ -33,9 +33,7 @@ const ( MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm ) -const ( - abortIndex int8 = math.MaxInt8 / 2 -) +const abortIndex int8 = math.MaxInt8 / 2 // Context is the most important part of gin. It allows us to pass variables between middleware, // manage the flow, validate the JSON of a request and render a JSON response for example. @@ -105,8 +103,7 @@ func (c *Context) Handler() HandlerFunc { // See example in GitHub. func (c *Context) Next() { c.index++ - s := int8(len(c.handlers)) - for ; c.index < s; c.index++ { + for s := int8(len(c.handlers)); c.index < s; c.index++ { c.handlers[c.index](c) } } @@ -521,11 +518,11 @@ func (c *Context) ClientIP() string { clientIP = clientIP[0:index] } clientIP = strings.TrimSpace(clientIP) - if len(clientIP) > 0 { + if clientIP != "" { return clientIP } clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip")) - if len(clientIP) > 0 { + if clientIP != "" { return clientIP } } @@ -588,7 +585,7 @@ func (c *Context) Status(code int) { // It writes a header in the response. // If value == "", this method removes the header `c.Writer.Header().Del(key)` func (c *Context) Header(key, value string) { - if len(value) == 0 { + if value == "" { c.Writer.Header().Del(key) } else { c.Writer.Header().Set(key, value) diff --git a/gin.go b/gin.go index 4857f3ae..b59712bf 100644 --- a/gin.go +++ b/gin.go @@ -403,8 +403,8 @@ func redirectTrailingSlash(c *Context) { code = 307 } - if len(path) > 1 && path[len(path)-1] == '/' { - req.URL.Path = path[:len(path)-1] + if length := len(path); length > 1 && path[length-1] == '/' { + req.URL.Path = path[:length-1] } else { req.URL.Path = path + "/" } diff --git a/json/json.go b/json/json.go index d2d0f8b3..aa76aa30 100644 --- a/json/json.go +++ b/json/json.go @@ -6,9 +6,7 @@ package json -import ( - "encoding/json" -) +import "encoding/json" var ( Marshal = json.Marshal diff --git a/json/jsoniter.go b/json/jsoniter.go index 65deee59..ffe1424a 100644 --- a/json/jsoniter.go +++ b/json/jsoniter.go @@ -6,9 +6,7 @@ package json -import ( - "github.com/json-iterator/go" -) +import "github.com/json-iterator/go" var ( json = jsoniter.ConfigCompatibleWithStandardLibrary From 80f691159fa5821b654b4b78cee8a5cb4316fcd7 Mon Sep 17 00:00:00 2001 From: Andrii Bubis Date: Sun, 12 Nov 2017 07:37:32 +0200 Subject: [PATCH 040/303] Added simple testing documentation and examples (#1156) --- README.md | 46 +++++++++++++++++++++++++++++++++++++ examples/basic/main.go | 7 +++++- examples/basic/main_test.go | 20 ++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 examples/basic/main_test.go diff --git a/README.md b/README.md index 6b8cdd7e..bffcce37 100644 --- a/README.md +++ b/README.md @@ -1344,6 +1344,52 @@ func main() { } ``` +## Testing + +The `net/http/httptest` package is preferable way for HTTP testing. + +```go +package main + +func setupRouter() *gin.Engine { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + return r +} + +func main() { + r := setupRouter() + r.Run(":8080") +} +``` + +Test for code example above: + +```go +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPingRoute(t *testing.T) { + router := setupRouter() + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/ping", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "pong", w.Body.String()) +} +``` + ## Users [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. diff --git a/examples/basic/main.go b/examples/basic/main.go index 984c06ab..473c6a09 100644 --- a/examples/basic/main.go +++ b/examples/basic/main.go @@ -6,7 +6,7 @@ import ( var DB = make(map[string]string) -func main() { +func setupRouter() *gin.Engine { // Disable Console Color // gin.DisableConsoleColor() r := gin.Default() @@ -53,6 +53,11 @@ func main() { } }) + return r +} + +func main() { + r := setupRouter() // Listen and Server in 0.0.0.0:8080 r.Run(":8080") } diff --git a/examples/basic/main_test.go b/examples/basic/main_test.go new file mode 100644 index 00000000..61203d66 --- /dev/null +++ b/examples/basic/main_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPingRoute(t *testing.T) { + router := setupRouter() + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/ping", nil) + router.ServeHTTP(w, req) + + assert.Equal(t, 200, w.Code) + assert.Equal(t, "pong", w.Body.String()) +} From ae9f03e6e80212e45427f811683f2a512cc8f506 Mon Sep 17 00:00:00 2001 From: Richard Lee Date: Sun, 12 Nov 2017 13:56:59 +0800 Subject: [PATCH 041/303] Fix up syntax error in README (#1155) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bffcce37..7d3b9194 100644 --- a/README.md +++ b/README.md @@ -472,7 +472,7 @@ func main() { // Example for binding JSON ({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login - if err = c.ShouldBindJSON(&json); err == nil { + if err := c.ShouldBindJSON(&json); err == nil { if json.User == "manu" && json.Password == "123" { c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) } else { From 1f377cb847977aeeed30c3e77377c8a9ff7ccff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 21 Nov 2017 09:27:57 +0800 Subject: [PATCH 042/303] Add test cases for RunTLS and each mode (#1173) * add RunTLS test cases and add debug/test mode cases * add release mode cases --- fixtures/testdata/cert.pem | 18 ++++++ fixtures/testdata/key.pem | 27 ++++++++ gin_test.go | 128 +++++++++++++++++++++++++++++++------ 3 files changed, 155 insertions(+), 18 deletions(-) create mode 100644 fixtures/testdata/cert.pem create mode 100644 fixtures/testdata/key.pem diff --git a/fixtures/testdata/cert.pem b/fixtures/testdata/cert.pem new file mode 100644 index 00000000..c1d3d632 --- /dev/null +++ b/fixtures/testdata/cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC9DCCAdygAwIBAgIQUNSK+OxWHYYFxHVJV0IlpDANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMB4XDTE3MTExNjEyMDA0N1oXDTE4MTExNjEyMDA0 +N1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAKmyj/YZpD59Bpy4w3qf6VzMw9uUBsWp+IP4kl7z5cmGHYUHn/YopTLH +vR23GAB12p6Km5QWzCBuJF4j61PJXHfg3/rjShZ77JcQ3kzxuy1iKDI+DNKN7Klz +rdjJ49QD0lczZHeBvvCL7JsJFKFjGy62rnStuW8LaIEdtjXT+GUZTxJh6G7yPYfD +MS1IsdMQGOdbGwNa+qogMuQPh0TzHw+C73myKrjY6pREijknMC/rnIMz9dLPt6Kl +xXy4br443dpY6dYGIhDuKhROT+vZ05HKasuuQUFhY7v/KoUpEZMB9rfUSzjQ5fak +eDUAMniXRcd+DmwvboG2TI6ixmuPK+ECAwEAAaNGMEQwDgYDVR0PAQH/BAQDAgWg +MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDwYDVR0RBAgwBocE +fwAAATANBgkqhkiG9w0BAQsFAAOCAQEAMXOLvj7BFsxdbcfRPBd0OFrH/8lI7vPV +LRcJ6r5iv0cnNvZXXbIOQLbg4clJAWjoE08nRm1KvNXhKdns0ELEV86YN2S6jThq +rIGrBqKtaJLB3M9BtDSiQ6SGPLYrWvmhj3Avi8PbSGy51bpGbqopd16j6LYU7Cp2 +TefMRlOAFtHojpCVon1CMpqcNxS0WNlQ3lUBSrw3HB0o12x++roja2ibF54tSHXB +KUuadoEzN+mMBwenEBychmAGzdiG4GQHRmhigh85+mtW6UMGiqyCZHs0EgE9FCLL +sRrsTI/VOzLz6lluygXkOsXrP+PP0SvmE3eylWjj9e2nj/u/Cy2YKg== +-----END CERTIFICATE----- diff --git a/fixtures/testdata/key.pem b/fixtures/testdata/key.pem new file mode 100644 index 00000000..c2a0181f --- /dev/null +++ b/fixtures/testdata/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAqbKP9hmkPn0GnLjDep/pXMzD25QGxan4g/iSXvPlyYYdhQef +9iilMse9HbcYAHXanoqblBbMIG4kXiPrU8lcd+Df+uNKFnvslxDeTPG7LWIoMj4M +0o3sqXOt2Mnj1APSVzNkd4G+8IvsmwkUoWMbLraudK25bwtogR22NdP4ZRlPEmHo +bvI9h8MxLUix0xAY51sbA1r6qiAy5A+HRPMfD4LvebIquNjqlESKOScwL+ucgzP1 +0s+3oqXFfLhuvjjd2ljp1gYiEO4qFE5P69nTkcpqy65BQWFju/8qhSkRkwH2t9RL +ONDl9qR4NQAyeJdFx34ObC9ugbZMjqLGa48r4QIDAQABAoIBAD5mhd+GMEo2KU9J +9b/Ku8I/HapJtW/L/7Fvn0tBPncrVQGM+zpGWfDhV95sbGwG6lwwNeNvuqIWPlNL +vAY0XkdKrrIQEDdSXH50WnpKzXxzwrou7QIj5Cmvevbjzl4xBZDBOilj0XWczmV4 +IljyG5XC4UXQeAaoWEZaSZ1jk8yAt2Zq1Hgg7HqhHsK/arWXBgax+4K5nV/s9gZx +yjKU9mXTIs7k/aNnZqwQKqcZF+l3mvbZttOaFwsP14H0I8OFWhnM9hie54Dejqxi +f4/llNxDqUs6lqJfP3qNxtORLcFe75M+Yl8v7g2hkjtLdZBakPzSTEx3TAK/UHgi +aM8DdxECgYEA3fmg/PI4EgUEj0C3SCmQXR/CnQLMUQgb54s0asp4akvp+M7YCcr1 +pQd3HFUpBwhBcJg5LeSe87vLupY7pHCKk56cl9WY6hse0b9sP/7DWJuGiO62m0E0 +vNjQ2jpG99oR2ROIHHeWsGCpGLmrRT/kY+vR3M+AOLZniXlOCw8k0aUCgYEAw7WL +XFWLxgZYQYilywqrQmfv1MBfaUCvykO6oWB+f6mmnihSFjecI+nDw/b3yXVYGEgy +0ebkuw0jP8suC8wBqX9WuXj+9nZNomJRssJyOMiEhDEqUiTztFPSp9pdruoakLTh +Wk1p9NralOqGPUmxpXlFKVmYRTUbluikVxDypI0CgYBn6sqEQH0hann0+o4TWWn9 +PrYkPUAbm1k8771tVTZERR/W3Dbldr/DL5iCihe39BR2urziEEqdvkglJNntJMar +TzDuIBADYQjvltb9qq4XGFBGYMLaMg+XbUVxNKEuvUdnwa4R7aZ9EfN34MwekkfA +w5Cu9/GGG1ajVEfGA6PwBQKBgA3o71jGs8KFXOx7e90sivOTU5Z5fc6LTHNB0Rf7 +NcJ5GmCPWRY/KZfb25AoE4B8GKDRMNt+X69zxZeZJ1KrU0rqxA02rlhyHB54gnoE +G/4xMkn6/JkOC0w70PMhMBtohC7YzFOQwQEoNPT0nkno3Pl33xSLS6lPlwBo1JVj +nPtZAoGACXNLXYkR5vexE+w6FGl59r4RQhu1XU8Mr5DIHeB7kXPN3RKbS201M+Tb +SB5jbu0iDV477XkzSNmhaksFf2wM9MT6CaE+8n3UU5tMa+MmBGgwYTp/i9HkqVh5 +jjpJifn1VWBINd4cpNzwCg9LXoo0tbtUPWwGzqVeyo/YE5GIHGo= +-----END RSA PRIVATE KEY----- diff --git a/gin_test.go b/gin_test.go index bdf5a9a9..57985dce 100644 --- a/gin_test.go +++ b/gin_test.go @@ -5,6 +5,7 @@ package gin import ( + "crypto/tls" "fmt" "html/template" "io/ioutil" @@ -21,9 +22,9 @@ func formatAsDate(t time.Time) string { return fmt.Sprintf("%d/%02d/%02d", year, month, day) } -func setupHTMLFiles(t *testing.T) func() { +func setupHTMLFiles(t *testing.T, mode string, tls bool) func() { go func() { - SetMode(TestMode) + SetMode(mode) router := New() router.Delims("{[{", "}]}") router.SetFuncMap(template.FuncMap{ @@ -38,16 +39,21 @@ func setupHTMLFiles(t *testing.T) func() { "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) - router.Run(":8888") + if tls { + // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` + router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem") + } else { + router.Run(":8888") + } }() t.Log("waiting 1 second for server startup") time.Sleep(1 * time.Second) return func() {} } -func setupHTMLGlob(t *testing.T) func() { +func setupHTMLGlob(t *testing.T, mode string, tls bool) func() { go func() { - SetMode(DebugMode) + SetMode(mode) router := New() router.Delims("{[{", "}]}") router.SetFuncMap(template.FuncMap{ @@ -62,16 +68,20 @@ func setupHTMLGlob(t *testing.T) func() { "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) - router.Run(":8888") + if tls { + // these files generated by `go run $GOROOT/src/crypto/tls/generate_cert.go --host 127.0.0.1` + router.RunTLS(":9999", "./fixtures/testdata/cert.pem", "./fixtures/testdata/key.pem") + } else { + router.Run(":8888") + } }() t.Log("waiting 1 second for server startup") time.Sleep(1 * time.Second) return func() {} } -//TODO func TestLoadHTMLGlob(t *testing.T) { - td := setupHTMLGlob(t) + td := setupHTMLGlob(t, DebugMode, false) res, err := http.Get("http://127.0.0.1:8888/test") if err != nil { fmt.Println(err) @@ -83,9 +93,55 @@ func TestLoadHTMLGlob(t *testing.T) { td() } +func TestLoadHTMLGlob2(t *testing.T) { + td := setupHTMLGlob(t, TestMode, false) + res, err := http.Get("http://127.0.0.1:8888/test") + if err != nil { + fmt.Println(err) + } + + resp, _ := ioutil.ReadAll(res.Body) + assert.Equal(t, "

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello thinkerou

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

Hello thinkerou

") + assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") +} + +func TestRenderHTMLDebugPanics(t *testing.T) { + htmlRender := HTMLDebug{Files: nil, + Glob: "", + Delims: Delims{"{{", "}}"}, + FuncMap: nil, + } + assert.Panics(t, func() { htmlRender.Instance("", nil) }) +} From ae22f0c87091eb75e08429082935ec48035320ac Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Thu, 22 Feb 2018 21:00:20 +0800 Subject: [PATCH 059/303] chore(travis): add 1.10 version (#1256) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index ec12cad6..63b5d113 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ go: - 1.7.x - 1.8.x - 1.9.x + - 1.10.x - master git: From cbb1ee80b1c2a53c46872e43ed15c664917eb905 Mon Sep 17 00:00:00 2001 From: README Bot <35302948+codetriage-readme-bot@users.noreply.github.com> Date: Thu, 22 Feb 2018 07:28:50 -0600 Subject: [PATCH 060/303] Add CodeTriage badge to gin-gonic/gin (#1249) Adds a badge showing the number of people helping this repo on CodeTriage. [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) ## What is CodeTriage? CodeTriage is an Open Source app that is designed to make contributing to Open Source projects easier. It works by sending subscribers a few open issues in their inbox. If subscribers get busy, there is an algorithm that backs off issue load so they do not get overwhelmed [Read more about the CodeTriage project](https://www.codetriage.com/what). ## Why am I getting this PR? Your project was picked by the human, @schneems. They selected it from the projects submitted to https://www.codetriage.com and hand edited the PR. How did your project get added to [CodeTriage](https://www.codetriage.com/what)? Roughly 6 months ago, [dinsaw](https://github.com/dinsaw) added this project to CodeTriage in order to start contributing. Since then, 5 people have subscribed to help this repo. ## What does adding a badge accomplish? Adding a badge invites people to help contribute to your project. It also lets developers know that others are invested in the longterm success and maintainability of the project. You can see an example of a CodeTriage badge on these popular OSS READMEs: - [![](https://www.codetriage.com/rails/rails/badges/users.svg)](https://www.codetriage.com/rails/rails) https://github.com/rails/rails - [![](https://www.codetriage.com/crystal-lang/crystal/badges/users.svg)](https://www.codetriage.com/crystal-lang/crystal) https://github.com/crystal-lang/crystal ## Have a question or comment? While I am a bot, this PR was manually reviewed and monitored by a human - @schneems. My job is writing commit messages and handling PR logistics. If you have any questions, you can reply back to this PR and they will be answered by @schneems. If you do not want a badge right now, no worries, close the PR, you will not hear from me again. Thanks for making your project Open Source! Any feedback is greatly appreciated. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6e421f02..b51aa10c 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin) [![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. From 5d3f30cfc81f9109850fa9043a19783cbbde68a5 Mon Sep 17 00:00:00 2001 From: Mario Kostelac Date: Fri, 23 Feb 2018 01:09:33 +0000 Subject: [PATCH 061/303] Make "" mode being the same as debug mode (#1250) Not setting mode explicitly sets gin into debug mode, but it does not make it possible to retrieve gin mode as Debug since it's set to "". --- mode.go | 3 +++ mode_test.go | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/mode.go b/mode.go index 9c4f0246..9df4e45f 100644 --- a/mode.go +++ b/mode.go @@ -53,6 +53,9 @@ func SetMode(value string) { default: panic("gin mode unknown: " + value) } + if value == "" { + value = DebugMode + } modeName = value } diff --git a/mode_test.go b/mode_test.go index 7eaca823..cf27acd8 100644 --- a/mode_test.go +++ b/mode_test.go @@ -21,6 +21,10 @@ func TestSetMode(t *testing.T) { assert.Equal(t, TestMode, Mode()) os.Unsetenv(ENV_GIN_MODE) + SetMode("") + assert.Equal(t, debugCode, ginMode) + assert.Equal(t, DebugMode, Mode()) + SetMode(DebugMode) assert.Equal(t, debugCode, ginMode) assert.Equal(t, DebugMode, Mode()) From 3e3f9bca81da5a4d2664d075012ae5aca9e70b9e Mon Sep 17 00:00:00 2001 From: Romain Beuque Date: Tue, 20 Mar 2018 07:05:24 +0100 Subject: [PATCH 062/303] doc(graceful-shutdown): failure to ListenAndServe should be a reason to exit (#1287) Signed-off-by: Romain Beuque --- README.md | 4 ++-- examples/graceful-shutdown/graceful-shutdown/server.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b51aa10c..72ac99bd 100644 --- a/README.md +++ b/README.md @@ -1324,8 +1324,8 @@ func main() { go func() { // service connections - if err := srv.ListenAndServe(); err != nil { - log.Printf("listen: %s\n", err) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) } }() diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go index 6debe7f5..af4f2146 100644 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -27,8 +27,8 @@ func main() { go func() { // service connections - if err := srv.ListenAndServe(); err != nil { - log.Printf("listen: %s\n", err) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) } }() From 65a65c2edd4e86f96634a553f07a25c38f2b1c75 Mon Sep 17 00:00:00 2001 From: hellojukay Date: Tue, 20 Mar 2018 14:42:51 +0800 Subject: [PATCH 063/303] add gin panic time log (#1270) * add gin pinic time log * Update recovery.go --- recovery.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index 89b39fec..dcbeba74 100644 --- a/recovery.go +++ b/recovery.go @@ -12,6 +12,7 @@ import ( "log" "net/http/httputil" "runtime" + "time" ) var ( @@ -38,7 +39,7 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc { if logger != nil { stack := stack(3) httprequest, _ := httputil.DumpRequest(c.Request, false) - logger.Printf("[Recovery] panic recovered:\n%s\n%s\n%s%s", string(httprequest), err, stack, reset) + logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) } c.AbortWithStatus(500) } @@ -107,3 +108,8 @@ func function(pc uintptr) []byte { name = bytes.Replace(name, centerDot, dot, -1) return name } + +func timeFormat(t time.Time) string { + var timeString = t.Format("2006/01/02 - 15:04:05") + return timeString +} From 6d913fc343cfac80d656db5be67a0ffb1323ba0d Mon Sep 17 00:00:00 2001 From: Suhas Karanth Date: Thu, 29 Mar 2018 12:03:07 +0530 Subject: [PATCH 064/303] fix(binding): Expose validator engine used by the default Validator (#1277) * fix(binding): Expose validator engine used by the default Validator - Add func ValidatorEngine for returning the underlying validator engine used in the default StructValidator implementation. - Remove the function RegisterValidation from the StructValidator interface which made it immpossible to use a StructValidator implementation without the validator.v8 library. - Update and rename test for registering validation Test{RegisterValidation => ValidatorEngine}. - Update readme and example for registering custom validation. - Add example for registering struct level validation. - Add documentation for the following binding funcs/types: - Binding interface - StructValidator interface - Validator instance - Binding implementations - Default func * fix(binding): Move validator engine getter inside interface * docs: rm date cmd from custom validation demo --- README.md | 13 +++-- binding/binding.go | 23 +++++--- binding/default_validator.go | 8 ++- binding/json.go | 3 ++ binding/validate_test.go | 9 ++-- examples/custom-validation/server.go | 6 ++- examples/struct-lvl-validations/README.md | 50 ++++++++++++++++++ examples/struct-lvl-validations/server.go | 64 +++++++++++++++++++++++ 8 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 examples/struct-lvl-validations/README.md create mode 100644 examples/struct-lvl-validations/server.go diff --git a/README.md b/README.md index 72ac99bd..7dd7f734 100644 --- a/README.md +++ b/README.md @@ -564,7 +564,11 @@ func bookableDate( func main() { route := gin.Default() - binding.Validator.RegisterValidation("bookabledate", bookableDate) + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + route.GET("/bookable", getBookable) route.Run(":8085") } @@ -580,13 +584,16 @@ func getBookable(c *gin.Context) { ``` ```console -$ curl "localhost:8085/bookable?check_in=2017-08-16&check_out=2017-08-17" +$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17" {"message":"Booking dates are valid!"} -$ curl "localhost:8085/bookable?check_in=2017-08-15&check_out=2017-08-16" +$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} ``` +[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way. +See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more. + ### Only Bind Query String `ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). diff --git a/binding/binding.go b/binding/binding.go index dc32d538..646eb80a 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -6,8 +6,6 @@ package binding import ( "net/http" - - "gopkg.in/go-playground/validator.v8" ) const ( @@ -23,11 +21,18 @@ const ( MIMEMSGPACK2 = "application/msgpack" ) +// Binding describes the interface which needs to be implemented for binding the +// data present in the request such as JSON request body, query parameters or +// the form POST. type Binding interface { Name() string Bind(*http.Request, interface{}) error } +// StructValidator is the minimal interface which needs to be implemented in +// order for it to be used as the validator engine for ensuring the correctness +// of the reqest. Gin provides a default implementation for this using +// https://github.com/go-playground/validator/tree/v8.18.2. type StructValidator interface { // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // If the received type is not a struct, any validation should be skipped and nil must be returned. @@ -36,14 +41,18 @@ type StructValidator interface { // Otherwise nil must be returned. ValidateStruct(interface{}) error - // RegisterValidation adds a validation Func to a Validate's map of validators denoted by the key - // NOTE: if the key already exists, the previous validation function will be replaced. - // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation - RegisterValidation(string, validator.Func) error + // Engine returns the underlying validator engine which powers the + // StructValidator implementation. + Engine() interface{} } +// Validator is the default validator which implements the StructValidator +// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2 +// under the hood. var Validator StructValidator = &defaultValidator{} +// These implement the Binding interface and can be used to bind the data +// present in the request to struct instances. var ( JSON = jsonBinding{} XML = xmlBinding{} @@ -55,6 +64,8 @@ var ( MsgPack = msgpackBinding{} ) +// Default returns the appropriate Binding instance based on the HTTP method +// and the content type. func Default(method, contentType string) Binding { if method == "GET" { return Form diff --git a/binding/default_validator.go b/binding/default_validator.go index 6336bb6e..c67aa8a3 100644 --- a/binding/default_validator.go +++ b/binding/default_validator.go @@ -28,9 +28,13 @@ func (v *defaultValidator) ValidateStruct(obj interface{}) error { return nil } -func (v *defaultValidator) RegisterValidation(key string, fn validator.Func) error { +// Engine returns the underlying validator engine which powers the default +// Validator instance. This is useful if you want to register custom validations +// or struct level validations. See validator GoDoc for more info - +// https://godoc.org/gopkg.in/go-playground/validator.v8 +func (v *defaultValidator) Engine() interface{} { v.lazyinit() - return v.validate.RegisterValidation(key, fn) + return v.validate } func (v *defaultValidator) lazyinit() { diff --git a/binding/json.go b/binding/json.go index b7c856af..e928a8c1 100644 --- a/binding/json.go +++ b/binding/json.go @@ -10,6 +10,9 @@ import ( "github.com/gin-gonic/gin/json" ) +// EnableDecoderUseNumber is used to call the UseNumber method on the JSON +// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an +// interface{} as a Number instead of as a float64. var EnableDecoderUseNumber = false type jsonBinding struct{} diff --git a/binding/validate_test.go b/binding/validate_test.go index 8ca79989..cb76063c 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -214,11 +214,14 @@ func notOne( return false } -func TestRegisterValidation(t *testing.T) { +func TestValidatorEngine(t *testing.T) { // This validates that the function `notOne` matches // the expected function signature by `defaultValidator` // and by extension the validator library. - err := Validator.RegisterValidation("notone", notOne) + engine, ok := Validator.Engine().(*validator.Validate) + assert.True(t, ok) + + err := engine.RegisterValidation("notone", notOne) // Check that we can register custom validation without error assert.Nil(t, err) @@ -228,6 +231,6 @@ func TestRegisterValidation(t *testing.T) { // Check that we got back non-nil errs assert.NotNil(t, errs) - // Check that the error matches expactation + // Check that the error matches expectation assert.Error(t, errs, "", "", "notone") } diff --git a/examples/custom-validation/server.go b/examples/custom-validation/server.go index 31d449f0..dea0c302 100644 --- a/examples/custom-validation/server.go +++ b/examples/custom-validation/server.go @@ -30,7 +30,11 @@ func bookableDate( func main() { route := gin.Default() - binding.Validator.RegisterValidation("bookabledate", bookableDate) + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + route.GET("/bookable", getBookable) route.Run(":8085") } diff --git a/examples/struct-lvl-validations/README.md b/examples/struct-lvl-validations/README.md new file mode 100644 index 00000000..1bd57f03 --- /dev/null +++ b/examples/struct-lvl-validations/README.md @@ -0,0 +1,50 @@ +## Struct level validations + +Validations can also be registered at the `struct` level when field level validations +don't make much sense. This can also be used to solve cross-field validation elegantly. +Additionally, it can be combined with tag validations. Struct Level validations run after +the structs tag validations. + +### Example requests + +```shell +# Validation errors are generated for struct tags as well as at the struct level +$ curl -s -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{}' | jq +{ + "error": "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", + "message": "User validation failed!" +} + +# Validation fails at the struct level because neither first name nor last name are present +$ curl -s -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{"email": "george@vandaley.com"}' | jq +{ + "error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", + "message": "User validation failed!" +} + +# No validation errors when either first name or last name is present +$ curl -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{"fname": "George", "email": "george@vandaley.com"}' +{"message":"User validation successful."} + +$ curl -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{"lname": "Contanza", "email": "george@vandaley.com"}' +{"message":"User validation successful."} + +$ curl -X POST http://localhost:8085/user \ + -H 'content-type: application/json' \ + -d '{"fname": "George", "lname": "Costanza", "email": "george@vandaley.com"}' +{"message":"User validation successful."} +``` + +### Useful links + +- Validator docs - https://godoc.org/gopkg.in/go-playground/validator.v8#Validate.RegisterStructValidation +- Struct level example - https://github.com/go-playground/validator/blob/v8.18.2/examples/struct-level/struct_level.go +- Validator release notes - https://github.com/go-playground/validator/releases/tag/v8.7 diff --git a/examples/struct-lvl-validations/server.go b/examples/struct-lvl-validations/server.go new file mode 100644 index 00000000..be807b78 --- /dev/null +++ b/examples/struct-lvl-validations/server.go @@ -0,0 +1,64 @@ +package main + +import ( + "net/http" + "reflect" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + validator "gopkg.in/go-playground/validator.v8" +) + +// User contains user information. +type User struct { + FirstName string `json:"fname"` + LastName string `json:"lname"` + Email string `binding:"required,email"` +} + +// UserStructLevelValidation contains custom struct level validations that don't always +// make sense at the field validation level. For example, this function validates that either +// FirstName or LastName exist; could have done that with a custom field validation but then +// would have had to add it to both fields duplicating the logic + overhead, this way it's +// only validated once. +// +// NOTE: you may ask why wouldn't not just do this outside of validator. Doing this way +// hooks right into validator and you can combine with validation tags and still have a +// common error output format. +func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) { + user := structLevel.CurrentStruct.Interface().(User) + + if len(user.FirstName) == 0 && len(user.LastName) == 0 { + structLevel.ReportError( + reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname", + ) + structLevel.ReportError( + reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname", + ) + } + + // plus can to more, even with different tag than "fnameorlname" +} + +func main() { + route := gin.Default() + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterStructValidation(UserStructLevelValidation, User{}) + } + + route.POST("/user", validateUser) + route.Run(":8085") +} + +func validateUser(c *gin.Context) { + var u User + if err := c.ShouldBindJSON(&u); err == nil { + c.JSON(http.StatusOK, gin.H{"message": "User validation successful."}) + } else { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "User validation failed!", + "error": err.Error(), + }) + } +} From 6ad7b9c9d382844036334ad061b832e56269e14f Mon Sep 17 00:00:00 2001 From: Yoshiyuki Kinjo Date: Tue, 17 Apr 2018 11:54:40 +0900 Subject: [PATCH 065/303] Fix documentation typo (#1321) --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 99d19af4..278029d7 100644 --- a/utils.go +++ b/utils.go @@ -49,7 +49,7 @@ func WrapH(h http.Handler) HandlerFunc { } } -// H is a shortcup for map[string]interface{} +// H is a shortcut for map[string]interface{} type H map[string]interface{} // MarshalXML allows type H to be used with xml.Marshal. From 3455d7f38877a931d0b97bc7c800e6963351ed2f Mon Sep 17 00:00:00 2001 From: esplo Date: Thu, 19 Apr 2018 13:00:22 +0900 Subject: [PATCH 066/303] change README for app-engine example because of goapp deprecation (#1324) the App Engine SDK is superseded by the Cloud SDK --- examples/app-engine/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/app-engine/README.md b/examples/app-engine/README.md index 48505de8..b3dd7c78 100644 --- a/examples/app-engine/README.md +++ b/examples/app-engine/README.md @@ -1,7 +1,8 @@ # Guide to run Gin under App Engine LOCAL Development Server 1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.) -2. Download SDK for your platform from here: `https://developers.google.com/appengine/downloads?hl=es#Google_App_Engine_SDK_for_Go` +2. Download SDK for your platform from [here](https://cloud.google.com/appengine/docs/standard/go/download): `https://cloud.google.com/appengine/docs/standard/go/download` 3. Download Gin source code using: `$ go get github.com/gin-gonic/gin` -4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/` -5. Run it: `$ goapp serve app-engine/` \ No newline at end of file +4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/app-engine/` +5. Run it: `$ dev_appserver.py .` (notice that you have to run this script by Python2) + From 248c522e4a829f10cc525136d8e8f3d44f093ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 20 Apr 2018 09:54:00 +0800 Subject: [PATCH 067/303] Add Contents for README because it too long (#1325) --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 7dd7f734..38bde8d2 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,47 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi ![Gin console logger](https://gin-gonic.github.io/gin/other/console.png) +## Contents + +- [Quick start](#quick-start) +- [Benchmarks](#benchmarks) +- [Gin v1.stable](#gin-v1-stable) +- [Start using it](#start-using-it) +- [Build with jsoniter](#build-with-jsoniter) +- [API Examples](#api-examples) + - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) + - [Parameters in path](#parameters-in-path) + - [Querystring parameters](#querystring-parameters) + - [Multipart/Urlencoded Form](#multiparturlencoded-form) + - [Another example: query + post form](#another-example-query--post-form) + - [Upload files](#upload-files) + - [Grouping routes](#grouping-routes) + - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) + - [Using middleware](#using-middleware) + - [How to write log file](#how-to-write-log-file) + - [Model binding and validation](#model-binding-and-validation) + - [Custom Validators](#custom-validators) + - [Only Bind Query String](#only-bind-query-string) + - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Bind HTML checkboxes](#bind-html-checkboxes) + - [Multipart/Urlencoded binding](#multiparturlencoded-binding) + - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) + - [Serving static files](#serving-static-files) + - [HTML rendering](#html-rendering) + - [Multitemplate](#multitemplate) + - [Redirects](#redirects) + - [Custom Middleware](#custom-middleware) + - [Using BasicAuth() middleware](#using-basicauth-middleware) + - [Goroutines inside a middleware](#goroutines-inside-a-middleware) + - [Custom HTTP configuration](#custom-http-configuration) + - [Support Let's Encrypt](#support-lets-encrypt) + - [Run multiple service using Gin](#run-multiple-service-using-gin) + - [Graceful restart or stop](#graceful-restart-or-stop) +- [Testing](#testing) +- [Users](#users--) + +## Quick start + ```sh # assume the following codes in example.go file $ cat example.go From dfe37ea6f1b9127be4cff4822a1308b4349444e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 20 Apr 2018 10:27:44 +0800 Subject: [PATCH 068/303] unify assert.Equal usage (#1327) * unify assert.Equal usage * fix typo --- binding/binding_test.go | 164 +++++++++++++++++++-------------------- binding/validate_test.go | 6 +- render/render_test.go | 50 ++++++------ 3 files changed, 110 insertions(+), 110 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 0c0f9f81..b239cafb 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -140,26 +140,26 @@ type FooBarStructForFloat64Type struct { } func TestBindingDefault(t *testing.T) { - assert.Equal(t, Default("GET", ""), Form) - assert.Equal(t, Default("GET", MIMEJSON), Form) + assert.Equal(t, Form, Default("GET", "")) + assert.Equal(t, Form, Default("GET", MIMEJSON)) - assert.Equal(t, Default("POST", MIMEJSON), JSON) - assert.Equal(t, Default("PUT", MIMEJSON), JSON) + assert.Equal(t, JSON, Default("POST", MIMEJSON)) + assert.Equal(t, JSON, Default("PUT", MIMEJSON)) - assert.Equal(t, Default("POST", MIMEXML), XML) - assert.Equal(t, Default("PUT", MIMEXML2), XML) + assert.Equal(t, XML, Default("POST", MIMEXML)) + assert.Equal(t, XML, Default("PUT", MIMEXML2)) - assert.Equal(t, Default("POST", MIMEPOSTForm), Form) - assert.Equal(t, Default("PUT", MIMEPOSTForm), Form) + assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) + assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) - assert.Equal(t, Default("POST", MIMEMultipartPOSTForm), Form) - assert.Equal(t, Default("PUT", MIMEMultipartPOSTForm), Form) + assert.Equal(t, Form, Default("POST", MIMEMultipartPOSTForm)) + assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm)) - assert.Equal(t, Default("POST", MIMEPROTOBUF), ProtoBuf) - assert.Equal(t, Default("PUT", MIMEPROTOBUF), ProtoBuf) + assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) + assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) - assert.Equal(t, Default("POST", MIMEMSGPACK), MsgPack) - assert.Equal(t, Default("PUT", MIMEMSGPACK2), MsgPack) + assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) + assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) } func TestBindingJSON(t *testing.T) { @@ -445,9 +445,9 @@ func TestBindingFormPost(t *testing.T) { var obj FooBarStruct FormPost.Bind(req, &obj) - assert.Equal(t, FormPost.Name(), "form-urlencoded") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "form-urlencoded", FormPost.Name()) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) } func TestBindingFormPostFail(t *testing.T) { @@ -462,9 +462,9 @@ func TestBindingFormMultipart(t *testing.T) { var obj FooBarStruct FormMultipart.Bind(req, &obj) - assert.Equal(t, FormMultipart.Name(), "multipart/form-data") - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "multipart/form-data", FormMultipart.Name()) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) } func TestBindingFormMultipartFail(t *testing.T) { @@ -560,7 +560,7 @@ func TestExistsFails(t *testing.T) { func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} req := requestWithBody(method, path, body) @@ -569,8 +569,8 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) } err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) obj = FooBarStruct{} req = requestWithBody(method, badPath, badBody) @@ -580,7 +580,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) func TestFormBindingFail(t *testing.T) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) @@ -590,7 +590,7 @@ func TestFormBindingFail(t *testing.T) { func TestFormPostBindingFail(t *testing.T) { b := FormPost - assert.Equal(t, b.Name(), "form-urlencoded") + assert.Equal(t, "form-urlencoded", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) @@ -600,7 +600,7 @@ func TestFormPostBindingFail(t *testing.T) { func TestFormMultipartBindingFail(t *testing.T) { b := FormMultipart - assert.Equal(t, b.Name(), "multipart/form-data") + assert.Equal(t, "multipart/form-data", b.Name()) obj := FooBarStruct{} req, _ := http.NewRequest("POST", "/", nil) @@ -610,7 +610,7 @@ func TestFormMultipartBindingFail(t *testing.T) { func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooBarStructForTimeType{} req := requestWithBody(method, path, body) @@ -620,10 +620,10 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.TimeFoo.Unix(), int64(1510675200)) - assert.Equal(t, obj.TimeFoo.Location().String(), "Asia/Chongqing") - assert.Equal(t, obj.TimeBar.Unix(), int64(-62135596800)) - assert.Equal(t, obj.TimeBar.Location().String(), "UTC") + assert.Equal(t, int64(1510675200), obj.TimeFoo.Unix()) + assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String()) + assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix()) + assert.Equal(t, "UTC", obj.TimeBar.Location().String()) obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) @@ -633,7 +633,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeNotFormat{} req := requestWithBody(method, path, body) @@ -651,7 +651,7 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeFailFormat{} req := requestWithBody(method, path, body) @@ -669,7 +669,7 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := FooStructForTimeTypeFailLocation{} req := requestWithBody(method, path, body) @@ -687,7 +687,7 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := InvalidNameType{} req := requestWithBody(method, path, body) @@ -696,7 +696,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo } err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.TestName, "") + assert.Equal(t, "", obj.TestName) obj = InvalidNameType{} req = requestWithBody(method, badPath, badBody) @@ -706,7 +706,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badBody string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) obj := InvalidNameMapType{} req := requestWithBody(method, path, body) @@ -724,7 +724,7 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody string, typ string) { b := Form - assert.Equal(t, b.Name(), "form") + assert.Equal(t, "form", b.Name()) req := requestWithBody(method, path, body) if method == "POST" { @@ -735,8 +735,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForIntType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.IntFoo, int(0)) - assert.Equal(t, obj.IntBar, int(-12)) + assert.Equal(t, int(0), obj.IntFoo) + assert.Equal(t, int(-12), obj.IntBar) obj = FooBarStructForIntType{} req = requestWithBody(method, badPath, badBody) @@ -746,8 +746,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt8Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int8Foo, int8(0)) - assert.Equal(t, obj.Int8Bar, int8(-12)) + assert.Equal(t, int8(0), obj.Int8Foo) + assert.Equal(t, int8(-12), obj.Int8Bar) obj = FooBarStructForInt8Type{} req = requestWithBody(method, badPath, badBody) @@ -757,8 +757,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt16Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int16Foo, int16(0)) - assert.Equal(t, obj.Int16Bar, int16(-12)) + assert.Equal(t, int16(0), obj.Int16Foo) + assert.Equal(t, int16(-12), obj.Int16Bar) obj = FooBarStructForInt16Type{} req = requestWithBody(method, badPath, badBody) @@ -768,8 +768,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int32Foo, int32(0)) - assert.Equal(t, obj.Int32Bar, int32(-12)) + assert.Equal(t, int32(0), obj.Int32Foo) + assert.Equal(t, int32(-12), obj.Int32Bar) obj = FooBarStructForInt32Type{} req = requestWithBody(method, badPath, badBody) @@ -779,8 +779,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForInt64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Int64Foo, int64(0)) - assert.Equal(t, obj.Int64Bar, int64(-12)) + assert.Equal(t, int64(0), obj.Int64Foo) + assert.Equal(t, int64(-12), obj.Int64Bar) obj = FooBarStructForInt64Type{} req = requestWithBody(method, badPath, badBody) @@ -790,8 +790,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUintType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.UintFoo, uint(0x0)) - assert.Equal(t, obj.UintBar, uint(0xc)) + assert.Equal(t, uint(0x0), obj.UintFoo) + assert.Equal(t, uint(0xc), obj.UintBar) obj = FooBarStructForUintType{} req = requestWithBody(method, badPath, badBody) @@ -801,8 +801,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint8Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint8Foo, uint8(0x0)) - assert.Equal(t, obj.Uint8Bar, uint8(0xc)) + assert.Equal(t, uint8(0x0), obj.Uint8Foo) + assert.Equal(t, uint8(0xc), obj.Uint8Bar) obj = FooBarStructForUint8Type{} req = requestWithBody(method, badPath, badBody) @@ -812,8 +812,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint16Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint16Foo, uint16(0x0)) - assert.Equal(t, obj.Uint16Bar, uint16(0xc)) + assert.Equal(t, uint16(0x0), obj.Uint16Foo) + assert.Equal(t, uint16(0xc), obj.Uint16Bar) obj = FooBarStructForUint16Type{} req = requestWithBody(method, badPath, badBody) @@ -823,8 +823,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint32Foo, uint32(0x0)) - assert.Equal(t, obj.Uint32Bar, uint32(0xc)) + assert.Equal(t, uint32(0x0), obj.Uint32Foo) + assert.Equal(t, uint32(0xc), obj.Uint32Bar) obj = FooBarStructForUint32Type{} req = requestWithBody(method, badPath, badBody) @@ -834,8 +834,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForUint64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Uint64Foo, uint64(0x0)) - assert.Equal(t, obj.Uint64Bar, uint64(0xc)) + assert.Equal(t, uint64(0x0), obj.Uint64Foo) + assert.Equal(t, uint64(0xc), obj.Uint64Bar) obj = FooBarStructForUint64Type{} req = requestWithBody(method, badPath, badBody) @@ -845,8 +845,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForFloat32Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Float32Foo, float32(0.0)) - assert.Equal(t, obj.Float32Bar, float32(-12.34)) + assert.Equal(t, float32(0.0), obj.Float32Foo) + assert.Equal(t, float32(-12.34), obj.Float32Bar) obj = FooBarStructForFloat32Type{} req = requestWithBody(method, badPath, badBody) @@ -856,8 +856,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForFloat64Type{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Float64Foo, float64(0.0)) - assert.Equal(t, obj.Float64Bar, float64(-12.34)) + assert.Equal(t, float64(0.0), obj.Float64Foo) + assert.Equal(t, float64(-12.34), obj.Float64Bar) obj = FooBarStructForFloat64Type{} req = requestWithBody(method, badPath, badBody) @@ -867,8 +867,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForBoolType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.BoolFoo, false) - assert.Equal(t, obj.BoolBar, true) + assert.False(t, obj.BoolFoo) + assert.True(t, obj.BoolBar) obj = FooBarStructForBoolType{} req = requestWithBody(method, badPath, badBody) @@ -878,7 +878,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooStructForSliceType{} err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.SliceFoo, []int{1, 2}) + assert.Equal(t, []int{1, 2}, obj.SliceFoo) obj = FooStructForSliceType{} req = requestWithBody(method, badPath, badBody) @@ -897,7 +897,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Query - assert.Equal(t, b.Name(), "query") + assert.Equal(t, "query", b.Name()) obj := FooBarStruct{} req := requestWithBody(method, path, body) @@ -906,13 +906,13 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) } err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") - assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "foo", obj.Bar) } func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody string) { b := Query - assert.Equal(t, b.Name(), "query") + assert.Equal(t, "query", b.Name()) obj := FooStructForMapType{} req := requestWithBody(method, path, body) @@ -924,13 +924,13 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str } func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) @@ -939,7 +939,7 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody } func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) @@ -949,7 +949,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body // we hope it is int64(123) v, e := obj.Foo.(json.Number).Int64() assert.NoError(t, e) - assert.Equal(t, v, int64(123)) + assert.Equal(t, int64(123), v) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) @@ -958,7 +958,7 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body } func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) @@ -967,7 +967,7 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod assert.NoError(t, err) // it will return float64(123) if not use EnableDecoderUseNumber // maybe it is not hoped - assert.Equal(t, obj.Foo, float64(123)) + assert.Equal(t, float64(123), obj.Foo) obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) @@ -976,13 +976,13 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod } func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) err := b.Bind(req, &obj) assert.Error(t, err) - assert.Equal(t, obj.Foo, "") + assert.Equal(t, "", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) @@ -991,14 +991,14 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad } func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := example.Test{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEPROTOBUF) err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, *obj.Label, "yes") + assert.Equal(t, "yes", *obj.Label) obj = example.Test{} req = requestWithBody("POST", badPath, badBody) @@ -1014,7 +1014,7 @@ func (h hook) Read([]byte) (int, error) { } func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := example.Test{} req := requestWithBody("POST", path, body) @@ -1032,14 +1032,14 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body } func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { - assert.Equal(t, b.Name(), name) + assert.Equal(t, name, b.Name()) obj := FooStruct{} req := requestWithBody("POST", path, body) req.Header.Add("Content-Type", MIMEMSGPACK) err := b.Bind(req, &obj) assert.NoError(t, err) - assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) diff --git a/binding/validate_test.go b/binding/validate_test.go index cb76063c..2c76b6d6 100644 --- a/binding/validate_test.go +++ b/binding/validate_test.go @@ -176,7 +176,7 @@ func TestValidatePrimitives(t *testing.T) { obj := Object{"foo": "bar", "bar": 1} assert.NoError(t, validate(obj)) assert.NoError(t, validate(&obj)) - assert.Equal(t, obj, Object{"foo": "bar", "bar": 1}) + assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj) obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}} assert.NoError(t, validate(obj2)) @@ -185,12 +185,12 @@ func TestValidatePrimitives(t *testing.T) { nu := 10 assert.NoError(t, validate(nu)) assert.NoError(t, validate(&nu)) - assert.Equal(t, nu, 10) + assert.Equal(t, 10, nu) str := "value" assert.NoError(t, validate(str)) assert.NoError(t, validate(&str)) - assert.Equal(t, str, "value") + assert.Equal(t, "value", str) } // structCustomValidation is a helper struct we use to check that diff --git a/render/render_test.go b/render/render_test.go index 530e222a..d825d041 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -27,7 +27,7 @@ func TestRenderMsgPack(t *testing.T) { } (MsgPack{data}).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) err := (MsgPack{data}).Render(w) @@ -41,7 +41,7 @@ func TestRenderMsgPack(t *testing.T) { assert.NoError(t, err) assert.Equal(t, w.Body.String(), string(buf.Bytes())) - assert.Equal(t, w.Header().Get("Content-Type"), "application/msgpack; charset=utf-8") + assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderJSON(t *testing.T) { @@ -78,8 +78,8 @@ func TestRenderIndentedJSON(t *testing.T) { err := (IndentedJSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}") - assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8") + assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderIndentedJSONPanics(t *testing.T) { @@ -161,12 +161,12 @@ b: d: [3, 4] ` (YAML{data}).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") + assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) err := (YAML{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n") - assert.Equal(t, w.Header().Get("Content-Type"), "application/x-yaml; charset=utf-8") + assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String()) + assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) } type fail struct{} @@ -189,13 +189,13 @@ func TestRenderXML(t *testing.T) { } (XML{data}).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) err := (XML{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "bar") - assert.Equal(t, w.Header().Get("Content-Type"), "application/xml; charset=utf-8") + assert.Equal(t, "bar", w.Body.String()) + assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderRedirect(t *testing.T) { @@ -235,8 +235,8 @@ func TestRenderData(t *testing.T) { }).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "#!PNG some raw data") - assert.Equal(t, w.Header().Get("Content-Type"), "image/png") + assert.Equal(t, "#!PNG some raw data", w.Body.String()) + assert.Equal(t, "image/png", w.Header().Get("Content-Type")) } func TestRenderString(t *testing.T) { @@ -246,7 +246,7 @@ func TestRenderString(t *testing.T) { Format: "hello %s %d", Data: []interface{}{}, }).WriteContentType(w) - assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) err := (String{ Format: "hola %s %d", @@ -254,8 +254,8 @@ func TestRenderString(t *testing.T) { }).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "hola manu 2") - assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "hola manu 2", w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderStringLenZero(t *testing.T) { @@ -267,8 +267,8 @@ func TestRenderStringLenZero(t *testing.T) { }).Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "hola %s %d") - assert.Equal(t, w.Header().Get("Content-Type"), "text/plain; charset=utf-8") + assert.Equal(t, "hola %s %d", w.Body.String()) + assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLTemplate(t *testing.T) { @@ -283,8 +283,8 @@ func TestRenderHTMLTemplate(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "Hello alexandernyquist") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "Hello alexandernyquist", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLTemplateEmptyName(t *testing.T) { @@ -299,8 +299,8 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "Hello alexandernyquist") - assert.Equal(t, w.Header().Get("Content-Type"), "text/html; charset=utf-8") + assert.Equal(t, "Hello alexandernyquist", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugFiles(t *testing.T) { @@ -317,8 +317,8 @@ func TestRenderHTMLDebugFiles(t *testing.T) { err := instance.Render(w) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), "

Hello thinkerou

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

Hello thinkerou

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

Hello thinkerou

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

Hello thinkerou

", w.Body.String()) + assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type")) } func TestRenderHTMLDebugPanics(t *testing.T) { From 814ac9490a38bb955a742d8ea15fa140489ee658 Mon Sep 17 00:00:00 2001 From: JINNOUCHI Yasushi Date: Sun, 22 Apr 2018 16:04:38 +0900 Subject: [PATCH 069/303] Add example to build single binary with templates (#1328) --- .travis.yml | 2 +- README.md | 45 +++++++++++++++++++++ examples/assets-in-binary/README.md | 33 ++++++++++++++++ examples/assets-in-binary/assets.go | 34 ++++++++++++++++ examples/assets-in-binary/html/bar.tmpl | 4 ++ examples/assets-in-binary/html/index.tmpl | 4 ++ examples/assets-in-binary/main.go | 48 +++++++++++++++++++++++ vendor/vendor.json | 6 +++ 8 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 examples/assets-in-binary/README.md create mode 100644 examples/assets-in-binary/assets.go create mode 100644 examples/assets-in-binary/html/bar.tmpl create mode 100644 examples/assets-in-binary/html/index.tmpl create mode 100644 examples/assets-in-binary/main.go diff --git a/.travis.yml b/.travis.yml index 63b5d113..e9101568 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ go: - master git: - depth: 3 + depth: 10 install: - make install diff --git a/README.md b/README.md index 38bde8d2..6c8988e7 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Support Let's Encrypt](#support-lets-encrypt) - [Run multiple service using Gin](#run-multiple-service-using-gin) - [Graceful restart or stop](#graceful-restart-or-stop) + - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Testing](#testing) - [Users](#users--) @@ -1393,6 +1394,50 @@ func main() { } ``` +### Build a single binary with templates + +You can build a server into a single binary containing templates by using [go-assets][]. + +[go-assets]: https://github.com/jessevdk/go-assets + +```go +func main() { + r := gin.New() + + t, err := loadTemplate() + if err != nil { + panic(err) + } + r.SetHTMLTemplate(t) + + r.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/index.tmpl",nil) + }) + r.Run(":8080") +} + +// loadTemplate loads templates embedded by go-assets-builder +func loadTemplate() (*template.Template, error) { + t := template.New("") + for name, file := range Assets.Files { + if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { + continue + } + h, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + t, err = t.New(name).Parse(string(h)) + if err != nil { + return nil, err + } + } + return t, nil +} +``` + +See a complete example in the `examples/assets-in-binary` directory. + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/examples/assets-in-binary/README.md b/examples/assets-in-binary/README.md new file mode 100644 index 00000000..0c23bb0d --- /dev/null +++ b/examples/assets-in-binary/README.md @@ -0,0 +1,33 @@ +# Building a single binary containing templates + +This is a complete example to create a single binary with the +[gin-gonic/gin][gin] Web Server with HTML templates. + +[gin]: https://github.com/gin-gonic/gin + +## How to use + +### Prepare Packages + +``` +go get github.com/gin-gonic/gin +go get github.com/jessevdk/go-assets-builder +``` + +### Generate assets.go + +``` +go-assets-builder html -o assets.go +``` + +### Build the server + +``` +go build -o assets-in-binary +``` + +### Run + +``` +./assets-in-binary +``` diff --git a/examples/assets-in-binary/assets.go b/examples/assets-in-binary/assets.go new file mode 100644 index 00000000..dcc5c465 --- /dev/null +++ b/examples/assets-in-binary/assets.go @@ -0,0 +1,34 @@ +package main + +import ( + "time" + + "github.com/jessevdk/go-assets" +) + +var _Assetsbfa8d115ce0617d89507412d5393a462f8e9b003 = "\n\n

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

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

Hello, {{.Foo}}

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

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

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

Hello, {{.Foo}}

+ diff --git a/examples/assets-in-binary/main.go b/examples/assets-in-binary/main.go new file mode 100644 index 00000000..27bc3b17 --- /dev/null +++ b/examples/assets-in-binary/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "html/template" + "io/ioutil" + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.New() + t, err := loadTemplate() + if err != nil { + panic(err) + } + r.SetHTMLTemplate(t) + r.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/index.tmpl", gin.H{ + "Foo": "World", + }) + }) + r.GET("/bar", func(c *gin.Context) { + c.HTML(http.StatusOK, "/html/bar.tmpl", gin.H{ + "Bar": "World", + }) + }) + r.Run(":8080") +} + +func loadTemplate() (*template.Template, error) { + t := template.New("") + for name, file := range Assets.Files { + if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { + continue + } + h, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + t, err = t.New(name).Parse(string(h)) + if err != nil { + return nil, err + } + } + return t, nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 573abe24..5ace49df 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -33,6 +33,12 @@ "revision": "5a0f697c9ed9d68fef0116532c6e05cfeae00e55", "revisionTime": "2017-06-01T23:02:30Z" }, + { + "checksumSHA1": "Cq9h7eDNXXyR/qJPvO8/Rk4pmFg=", + "path": "github.com/jessevdk/go-assets", + "revision": "4f4301a06e153ff90e17793577ab6bf79f8dc5c5", + "revisionTime": "2016-09-21T14:41:39Z" + }, { "checksumSHA1": "Ajh8TemnItg4nn+jKmVcsMRALBc=", "path": "github.com/json-iterator/go", From 41f951e0cd126aa45a5cae1dc5c0d26f7cf80561 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 25 Apr 2018 16:24:03 +0800 Subject: [PATCH 070/303] support default value for form (#1138) * support default value for form * fix bug for nil interface * use SplitN and optimization code * add test case * add test cases for form(own default value) * fix invalid code * fix code indent * assert order --- binding/binding_test.go | 52 +++++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 17 +++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index b239cafb..ac826417 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -29,6 +29,11 @@ type FooBarStruct struct { Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } +type FooDefaultBarStruct struct { + FooStruct + Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"` +} + type FooStructUseNumber struct { Foo interface{} `json:"foo" binding:"required"` } @@ -195,6 +200,18 @@ func TestBindingForm2(t *testing.T) { "", "") } +func TestBindingFormDefaultValue(t *testing.T) { + testFormBindingDefaultValue(t, "POST", + "/", "/", + "foo=bar", "bar2=foo") +} + +func TestBindingFormDefaultValue2(t *testing.T) { + testFormBindingDefaultValue(t, "GET", + "/?foo=bar", "/?bar2=foo", + "", "") +} + func TestBindingFormForTime(t *testing.T) { testFormBindingForTime(t, "POST", "/", "/", @@ -407,6 +424,12 @@ func createFormPostRequest() *http.Request { return req } +func createDefaultFormPostRequest() *http.Request { + req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) + req.Header.Set("Content-Type", MIMEPOSTForm) + return req +} + func createFormPostRequestFail() *http.Request { req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar")) req.Header.Set("Content-Type", MIMEPOSTForm) @@ -450,6 +473,15 @@ func TestBindingFormPost(t *testing.T) { assert.Equal(t, "foo", obj.Bar) } +func TestBindingDefaultValueFormPost(t *testing.T) { + req := createDefaultFormPostRequest() + var obj FooDefaultBarStruct + FormPost.Bind(req, &obj) + + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "hello", obj.Bar) +} + func TestBindingFormPostFail(t *testing.T) { req := createFormPostRequestFail() var obj FooStructForMapType @@ -578,6 +610,26 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) assert.Error(t, err) } +func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, "form", b.Name()) + + obj := FooDefaultBarStruct{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, "bar", obj.Foo) + assert.Equal(t, "hello", obj.Bar) + + obj = FooDefaultBarStruct{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + func TestFormBindingFail(t *testing.T) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index dd8c6246..7c680d99 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -8,6 +8,7 @@ import ( "errors" "reflect" "strconv" + "strings" "time" ) @@ -23,6 +24,15 @@ func mapForm(ptr interface{}, form map[string][]string) error { structFieldKind := structField.Kind() inputFieldName := typeField.Tag.Get("form") + inputFieldNameList := strings.Split(inputFieldName, ",") + inputFieldName = inputFieldNameList[0] + var defaultValue string + if len(inputFieldNameList) > 1 { + defaultList := strings.SplitN(inputFieldNameList[1], "=", 2) + if defaultList[0] == "default" { + defaultValue = defaultList[1] + } + } if inputFieldName == "" { inputFieldName = typeField.Name @@ -38,8 +48,13 @@ func mapForm(ptr interface{}, form map[string][]string) error { } } inputValue, exists := form[inputFieldName] + if !exists { - continue + if defaultValue == "" { + continue + } + inputValue = make([]string, 1) + inputValue[0] = defaultValue } numElems := len(inputValue) From 8c2401829041c89ddd6d89a0820acdd19a53855e Mon Sep 17 00:00:00 2001 From: senhtry Date: Thu, 26 Apr 2018 11:52:19 +0800 Subject: [PATCH 071/303] Add Jsonp Support to Context (#1333) --- README.md | 23 +++++++++++++++++++++++ context.go | 7 +++++++ context_test.go | 14 ++++++++++++++ render/json.go | 32 ++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 37 +++++++++++++++++++++++++++++++++++++ 6 files changed, 114 insertions(+) mode change 100644 => 100755 context.go mode change 100644 => 100755 render/json.go mode change 100644 => 100755 render/render.go mode change 100644 => 100755 render/render_test.go diff --git a/README.md b/README.md index 6c8988e7..aba9ef31 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) + - [JSONP rendering](#jsonp) - [Serving static files](#serving-static-files) - [HTML rendering](#html-rendering) - [Multitemplate](#multitemplate) @@ -861,6 +862,28 @@ func main() { r.Run(":8080") } ``` +#### JSONP + +Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists. + +```go +func main() { + r := gin.Default() + + r.GET("/JSONP?callback=x", func(c *gin.Context) { + data := map[string]interface{}{ + "foo": "bar", + } + + //callback is x + // Will output : x({\"foo\":\"bar\"}) + c.JSONP(http.StatusOK, data) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` ### Serving static files diff --git a/context.go b/context.go old mode 100644 new mode 100755 index 90d4c6e5..be205f45 --- a/context.go +++ b/context.go @@ -670,6 +670,13 @@ func (c *Context) SecureJSON(code int, obj interface{}) { c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj}) } +// JSONP serializes the given struct as JSON into the response body. +// It add padding to response body to request data from a server residing in a different domain than the client. +// It also sets the Content-Type as "application/javascript". +func (c *Context) JSONP(code int, obj interface{}) { + c.Render(code, render.JsonpJSON{Callback: c.DefaultQuery("callback", ""), Data: obj}) +} + // JSON serializes the given struct as JSON into the response body. // It also sets the Content-Type as "application/json". func (c *Context) JSON(code int, obj interface{}) { diff --git a/context_test.go b/context_test.go index 9024cfc1..841d8af0 100644 --- a/context_test.go +++ b/context_test.go @@ -581,6 +581,20 @@ func TestContextRenderJSON(t *testing.T) { assert.Equal(t, "application/json; charset=utf-8", w.HeaderMap.Get("Content-Type")) } +// Tests that the response is serialized as JSONP +// and Content-Type is set to application/javascript +func TestContextRenderJSONP(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest("GET", "http://example.com/?callback=x", nil) + + c.JSONP(201, H{"foo": "bar"}) + + assert.Equal(t, 201, w.Code) + assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w.HeaderMap.Get("Content-Type")) +} + // Tests that no JSON is rendered if code is 204 func TestContextRenderNoContentJSON(t *testing.T) { w := httptest.NewRecorder() diff --git a/render/json.go b/render/json.go old mode 100644 new mode 100755 index eb2548e2..3a2e8b2f --- a/render/json.go +++ b/render/json.go @@ -6,6 +6,7 @@ package render import ( "bytes" + "html/template" "net/http" "github.com/gin-gonic/gin/json" @@ -24,9 +25,15 @@ type SecureJSON struct { Data interface{} } +type JsonpJSON struct { + Callback string + Data interface{} +} + type SecureJSONPrefix string var jsonContentType = []string{"application/json; charset=utf-8"} +var jsonpContentType = []string{"application/javascript; charset=utf-8"} func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { @@ -80,3 +87,28 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { func (r SecureJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } + +func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + ret, err := json.Marshal(r.Data) + if err != nil { + return err + } + + if r.Callback == "" { + w.Write(ret) + return nil + } + + callback := template.JSEscapeString(r.Callback) + w.Write([]byte(callback)) + w.Write([]byte("(")) + w.Write(ret) + w.Write([]byte(")")) + + return nil +} + +func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonpContentType) +} diff --git a/render/render.go b/render/render.go old mode 100644 new mode 100755 index 71852364..578f2784 --- a/render/render.go +++ b/render/render.go @@ -15,6 +15,7 @@ var ( _ Render = JSON{} _ Render = IndentedJSON{} _ Render = SecureJSON{} + _ Render = JsonpJSON{} _ Render = XML{} _ Render = String{} _ Render = Redirect{} diff --git a/render/render_test.go b/render/render_test.go old mode 100644 new mode 100755 index d825d041..c7b51ef4 --- a/render/render_test.go +++ b/render/render_test.go @@ -128,6 +128,43 @@ func TestRenderSecureJSONFail(t *testing.T) { assert.Error(t, err) } +func TestRenderJsonpJSON(t *testing.T) { + w1 := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + } + + (JsonpJSON{"x", data}).WriteContentType(w1) + assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) + + err1 := (JsonpJSON{"x", data}).Render(w1) + + assert.NoError(t, err1) + assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) + + w2 := httptest.NewRecorder() + datas := []map[string]interface{}{{ + "foo": "bar", + }, { + "bar": "foo", + }} + + err2 := (JsonpJSON{"x", datas}).Render(w2) + assert.NoError(t, err2) + assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String()) + assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) +} + +func TestRenderJsonpJSONFail(t *testing.T) { + w := httptest.NewRecorder() + data := make(chan int) + + // json: unsupported type: chan int + err := (JsonpJSON{"x", data}).Render(w) + assert.Error(t, err) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal From 2282be059be78f9f7f70a72ca4dd3bcfe5f972ee Mon Sep 17 00:00:00 2001 From: Alexander Lokhman Date: Thu, 26 Apr 2018 15:09:34 +0100 Subject: [PATCH 072/303] Add support of pointers in form binding (#1336) * Add support of pointers in form binding * Add tests for pointer form binding --- binding/binding_test.go | 38 ++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 6 ++++++ 2 files changed, 44 insertions(+) diff --git a/binding/binding_test.go b/binding/binding_test.go index ac826417..e29e6dfe 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -144,6 +144,15 @@ type FooBarStructForFloat64Type struct { Float64Bar float64 `form:"float64_bar" binding:"required"` } +type FooStructForStringPtrType struct { + PtrFoo *string `form:"ptr_foo"` + PtrBar *string `form:"ptr_bar" binding:"required"` +} + +type FooStructForMapPtrType struct { + PtrBar *map[string]interface{} `form:"ptr_bar"` +} + func TestBindingDefault(t *testing.T) { assert.Equal(t, Form, Default("GET", "")) assert.Equal(t, Form, Default("GET", MIMEJSON)) @@ -378,6 +387,14 @@ func TestBindingFormForType(t *testing.T) { testFormBindingForType(t, "GET", "/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3", "", "", "Float64") + + testFormBindingForType(t, "POST", + "/", "/", + "ptr_bar=test", "bar2=test", "Ptr") + + testFormBindingForType(t, "GET", + "/?ptr_bar=test", "/?bar2=test", + "", "", "Ptr") } func TestBindingQuery(t *testing.T) { @@ -944,6 +961,27 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj := FooStructForSliceMapType{} err := b.Bind(req, &obj) assert.Error(t, err) + case "Ptr": + obj := FooStructForStringPtrType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Nil(t, obj.PtrFoo) + assert.Equal(t, "test", *obj.PtrBar) + + obj = FooStructForStringPtrType{} + obj.PtrBar = new(string) + err = b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, "test", *obj.PtrBar) + + objErr := FooStructForMapPtrType{} + err = b.Bind(req, &objErr) + assert.Error(t, err) + + obj = FooStructForStringPtrType{} + req = requestWithBody(method, badPath, badBody) + err = b.Bind(req, &obj) + assert.Error(t, err) } } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 7c680d99..7d5c1022 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -112,6 +112,12 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V return setFloatField(val, 64, structField) case reflect.String: structField.SetString(val) + case reflect.Ptr: + if !structField.Elem().IsValid() { + structField.Set(reflect.New(structField.Type().Elem())) + } + structFieldElem := structField.Elem() + return setWithProperType(structFieldElem.Kind(), val, structFieldElem) default: return errors.New("Unknown type") } From bd4f73af679e7d645f6d0277258fa360eda96f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 1 May 2018 14:24:18 +0800 Subject: [PATCH 073/303] support struct pointer (#1342) * support struct pointer * add readme --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++ binding/binding_test.go | 50 ++++++++++++++++++++++ binding/form_mapping.go | 9 +++- 3 files changed, 151 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aba9ef31..cff9bc8a 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Run multiple service using Gin](#run-multiple-service-using-gin) - [Graceful restart or stop](#graceful-restart-or-stop) - [Build a single binary with templates](#build-a-single-binary-with-templates) + - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Testing](#testing) - [Users](#users--) @@ -1461,6 +1462,98 @@ func loadTemplate() (*template.Template, error) { See a complete example in the `examples/assets-in-binary` directory. +### Bind form-data request with custom struct + +The follow example using custom struct: + +```go +type StructA struct { + FieldA string `form:"field_a"` +} + +type StructB struct { + NestedStruct StructA + FieldB string `form:"field_b"` +} + +type StructC struct { + NestedStructPointer *StructA + FieldC string `form:"field_c"` +} + +type StructD struct { + NestedAnonyStruct struct { + FieldX string `form:"field_x"` + } + FieldD string `form:"field_d"` +} + +func GetDataB(c *gin.Context) { + var b StructB + c.Bind(&b) + c.JSON(200, gin.H{ + "a": b.NestedStruct, + "b": b.FieldB, + }) +} + +func GetDataC(c *gin.Context) { + var b StructC + c.Bind(&b) + c.JSON(200, gin.H{ + "a": b.NestedStructPointer, + "c": b.FieldC, + }) +} + +func GetDataD(c *gin.Context) { + var b StructD + c.Bind(&b) + c.JSON(200, gin.H{ + "x": b.NestedAnonyStruct, + "d": b.FieldD, + }) +} + +func main() { + r := gin.Default() + r.GET("/getb", GetDataB) + r.GET("/getc", GetDataC) + r.GET("/getd", GetDataD) + + r.Run() +} +``` + +Using the command `curl` command result: + +``` +$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" +{"a":{"FieldA":"hello"},"b":"world"} +$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" +{"a":{"FieldA":"hello"},"c":"world"} +$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" +{"d":"world","x":{"FieldX":"hello"}} +``` + +**NOTE**: NOT support the follow style struct: + +```go +type StructX struct { + X struct {} `form:"name_x"` // HERE have form +} + +type StructY struct { + Y StructX `form:"name_y"` // HERE hava form +} + +type StructZ struct { + Z *StructZ `form:"name_z"` // HERE hava form +} +``` + +In a word, only support nested custom struct which have no `form` now. + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/binding/binding_test.go b/binding/binding_test.go index e29e6dfe..d6899dbb 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -74,6 +74,18 @@ type FooStructForSliceType struct { SliceFoo []int `form:"slice_foo"` } +type FooStructForStructType struct { + StructFoo struct { + Idx int `form:"idx"` + } +} + +type FooStructForStructPointerType struct { + StructPointerFoo *struct { + Name string `form:"name"` + } +} + type FooStructForSliceMapType struct { // Unknown type: not support map SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` @@ -395,6 +407,22 @@ func TestBindingFormForType(t *testing.T) { testFormBindingForType(t, "GET", "/?ptr_bar=test", "/?bar2=test", "", "", "Ptr") + + testFormBindingForType(t, "POST", + "/", "/", + "idx=123", "id1=1", "Struct") + + testFormBindingForType(t, "GET", + "/?idx=123", "/?id1=1", + "", "", "Struct") + + testFormBindingForType(t, "POST", + "/", "/", + "name=thinkerou", "name1=ou", "StructPointer") + + testFormBindingForType(t, "GET", + "/?name=thinkerou", "/?name1=ou", + "", "", "StructPointer") } func TestBindingQuery(t *testing.T) { @@ -953,6 +981,28 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s req = requestWithBody(method, badPath, badBody) err = JSON.Bind(req, &obj) assert.Error(t, err) + case "Struct": + obj := FooStructForStructType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, + struct { + Idx int "form:\"idx\"" + }(struct { + Idx int "form:\"idx\"" + }{Idx: 123}), + obj.StructFoo) + case "StructPointer": + obj := FooStructForStructPointerType{} + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, + struct { + Name string "form:\"name\"" + }(struct { + Name string "form:\"name\"" + }{Name: "thinkerou"}), + *obj.StructPointerFoo) case "Map": obj := FooStructForMapType{} err := b.Bind(req, &obj) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 7d5c1022..6ff726df 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -36,9 +36,16 @@ func mapForm(ptr interface{}, form map[string][]string) error { if inputFieldName == "" { inputFieldName = typeField.Name - // if "form" tag is nil, we inspect if the field is a struct. + // if "form" tag is nil, we inspect if the field is a struct or struct pointer. // this would not make sense for JSON parsing but it does for a form // since data is flatten + if structFieldKind == reflect.Ptr { + if !structField.Elem().IsValid() { + structField.Set(reflect.New(structField.Type().Elem())) + } + structField = structField.Elem() + structFieldKind = structField.Kind() + } if structFieldKind == reflect.Struct { err := mapForm(structField.Addr().Interface(), form) if err != nil { From 6e09ef03b0d292c0a636edae960694d9ae3ade8d Mon Sep 17 00:00:00 2001 From: Aurelien Regat-Barrel Date: Fri, 11 May 2018 03:57:21 +0200 Subject: [PATCH 074/303] Fix typo in panic() message (extra single quote) (#1352) Also fix the same typo in a comment --- tree.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index 20e3704b..b6530665 100644 --- a/tree.go +++ b/tree.go @@ -232,7 +232,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { } else if i == len(path) { // Make node a (in-path) leaf if n.handlers != nil { - panic("handlers are already registered for path ''" + fullPath + "'") + panic("handlers are already registered for path '" + fullPath + "'") } n.handlers = handlers } @@ -247,7 +247,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers HandlersChain) { var offset int // already handled bytes of the path - // find prefix until first wildcard (beginning with ':'' or '*'') + // find prefix until first wildcard (beginning with ':' or '*') for i, max := 0, len(path); numParams > 0; i++ { c := path[i] if c != ':' && c != '*' { From 995fa8e9ce404a7a8dc293c90ec018367639ffa9 Mon Sep 17 00:00:00 2001 From: JINNOUCHI Yasushi Date: Fri, 11 May 2018 11:33:33 +0900 Subject: [PATCH 075/303] Fix #216: Enable to call binding multiple times in some formats (#1341) * Add interface to read body bytes in binding * Add BindingBody implementation for some binding * Fix to use `BindBodyBytesKey` for key * Revert "Fix to use `BindBodyBytesKey` for key" This reverts commit 2c82901ceab6ae53730a3cfcd9839bee11a08f13. * Use private-like key for body bytes * Add tests for BindingBody & ShouldBindBodyWith * Add note for README * Remove redundant space between sentences --- README.md | 59 ++++++++++++++++++++++++++ binding/binding.go | 7 ++++ binding/binding_body_test.go | 67 ++++++++++++++++++++++++++++++ binding/json.go | 12 +++++- binding/msgpack.go | 13 +++++- binding/protobuf.go | 15 +++---- binding/xml.go | 11 ++++- context.go | 25 +++++++++++ context_test.go | 80 ++++++++++++++++++++++++++++++++++++ 9 files changed, 279 insertions(+), 10 deletions(-) create mode 100644 binding/binding_body_test.go diff --git a/README.md b/README.md index cff9bc8a..70a5a13b 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Graceful restart or stop](#graceful-restart-or-stop) - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) + - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [Testing](#testing) - [Users](#users--) @@ -1554,6 +1555,64 @@ type StructZ struct { In a word, only support nested custom struct which have no `form` now. +### Try to bind body into different structs + +The normal methods for binding request body consumes `c.Request.Body` and they +cannot be called multiple times. + +```go +type formA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` +} + +type formB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` +} + +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This c.ShouldBind consumes c.Request.Body and it cannot be reused. + if errA := c.ShouldBind(&objA); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // Always an error is occurred by this because c.Request.Body is EOF now. + } else if errB := c.ShouldBind(&objB); errB == nil { + c.String(http.StatusOK, `the body should be formB`) + } else { + ... + } +} +``` + +For this, you can use `c.ShouldBindBodyWith`. + +```go +func SomeHandler(c *gin.Context) { + objA := formA{} + objB := formB{} + // This reads c.Request.Body and stores the result into the context. + if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { + c.String(http.StatusOK, `the body should be formA`) + // At this time, it reuses body stored in the context. + } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { + c.String(http.StatusOK, `the body should be formB JSON`) + // And it can accepts other formats + } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { + c.String(http.StatusOK, `the body should be formB XML`) + } else { + ... + } +} +``` + +* `c.ShouldBindBodyWith` stores body into the context before binding. This has +a slight impact to performance, so you should not use this method if you are +enough to call binding at once. +* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`, +`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`, +can be called by `c.ShouldBind()` multiple times without any damage to +performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/binding/binding.go b/binding/binding.go index 646eb80a..1a984777 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -29,6 +29,13 @@ type Binding interface { Bind(*http.Request, interface{}) error } +// BindingBody adds BindBody method to Binding. BindBody is similar with Bind, +// but it reads the body from supplied bytes instead of req.Body. +type BindingBody interface { + Binding + BindBody([]byte, interface{}) error +} + // StructValidator is the minimal interface which needs to be implemented in // order for it to be used as the validator engine for ensuring the correctness // of the reqest. Gin provides a default implementation for this using diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go new file mode 100644 index 00000000..c41d9f86 --- /dev/null +++ b/binding/binding_body_test.go @@ -0,0 +1,67 @@ +package binding + +import ( + "bytes" + "io/ioutil" + "testing" + + "github.com/gin-gonic/gin/binding/example" + "github.com/golang/protobuf/proto" + "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" +) + +func TestBindingBody(t *testing.T) { + for _, tt := range []struct { + name string + binding BindingBody + body string + want string + }{ + { + name: "JSON bidning", + binding: JSON, + body: `{"foo":"FOO"}`, + }, + { + name: "XML bidning", + binding: XML, + body: ` + + FOO +`, + }, + { + name: "MsgPack binding", + binding: MsgPack, + body: msgPackBody(t), + }, + } { + t.Logf("testing: %s", tt.name) + req := requestWithBody("POST", "/", tt.body) + form := FooStruct{} + body, _ := ioutil.ReadAll(req.Body) + assert.NoError(t, tt.binding.BindBody(body, &form)) + assert.Equal(t, FooStruct{"FOO"}, form) + } +} + +func msgPackBody(t *testing.T) string { + test := FooStruct{"FOO"} + h := new(codec.MsgpackHandle) + buf := bytes.NewBuffer(nil) + assert.NoError(t, codec.NewEncoder(buf, h).Encode(test)) + return buf.String() +} + +func TestBindingBodyProto(t *testing.T) { + test := example.Test{ + Label: proto.String("FOO"), + } + data, _ := proto.Marshal(&test) + req := requestWithBody("POST", "/", string(data)) + form := example.Test{} + body, _ := ioutil.ReadAll(req.Body) + assert.NoError(t, ProtoBuf.BindBody(body, &form)) + assert.Equal(t, test, form) +} diff --git a/binding/json.go b/binding/json.go index e928a8c1..fea17bb2 100644 --- a/binding/json.go +++ b/binding/json.go @@ -5,6 +5,8 @@ package binding import ( + "bytes" + "io" "net/http" "github.com/gin-gonic/gin/json" @@ -22,7 +24,15 @@ func (jsonBinding) Name() string { } func (jsonBinding) Bind(req *http.Request, obj interface{}) error { - decoder := json.NewDecoder(req.Body) + return decodeJSON(req.Body, obj) +} + +func (jsonBinding) BindBody(body []byte, obj interface{}) error { + return decodeJSON(bytes.NewReader(body), obj) +} + +func decodeJSON(r io.Reader, obj interface{}) error { + decoder := json.NewDecoder(r) if EnableDecoderUseNumber { decoder.UseNumber() } diff --git a/binding/msgpack.go b/binding/msgpack.go index 7faea4b5..b7f73197 100644 --- a/binding/msgpack.go +++ b/binding/msgpack.go @@ -5,6 +5,8 @@ package binding import ( + "bytes" + "io" "net/http" "github.com/ugorji/go/codec" @@ -17,7 +19,16 @@ func (msgpackBinding) Name() string { } func (msgpackBinding) Bind(req *http.Request, obj interface{}) error { - if err := codec.NewDecoder(req.Body, new(codec.MsgpackHandle)).Decode(&obj); err != nil { + return decodeMsgPack(req.Body, obj) +} + +func (msgpackBinding) BindBody(body []byte, obj interface{}) error { + return decodeMsgPack(bytes.NewReader(body), obj) +} + +func decodeMsgPack(r io.Reader, obj interface{}) error { + cdc := new(codec.MsgpackHandle) + if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil { return err } return validate(obj) diff --git a/binding/protobuf.go b/binding/protobuf.go index c7eb84e9..540e9c1f 100644 --- a/binding/protobuf.go +++ b/binding/protobuf.go @@ -17,19 +17,20 @@ func (protobufBinding) Name() string { return "protobuf" } -func (protobufBinding) Bind(req *http.Request, obj interface{}) error { - +func (b protobufBinding) Bind(req *http.Request, obj interface{}) error { buf, err := ioutil.ReadAll(req.Body) if err != nil { return err } + return b.BindBody(buf, obj) +} - if err = proto.Unmarshal(buf, obj.(proto.Message)); err != nil { +func (protobufBinding) BindBody(body []byte, obj interface{}) error { + if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil { return err } - - //Here it's same to return validate(obj), but util now we cann't add `binding:""` to the struct - //which automatically generate by gen-proto + // Here it's same to return validate(obj), but util now we cann't add + // `binding:""` to the struct which automatically generate by gen-proto return nil - //return validate(obj) + // return validate(obj) } diff --git a/binding/xml.go b/binding/xml.go index f84a6b7f..4e901149 100644 --- a/binding/xml.go +++ b/binding/xml.go @@ -5,7 +5,9 @@ package binding import ( + "bytes" "encoding/xml" + "io" "net/http" ) @@ -16,7 +18,14 @@ func (xmlBinding) Name() string { } func (xmlBinding) Bind(req *http.Request, obj interface{}) error { - decoder := xml.NewDecoder(req.Body) + return decodeXML(req.Body, obj) +} + +func (xmlBinding) BindBody(body []byte, obj interface{}) error { + return decodeXML(bytes.NewReader(body), obj) +} +func decodeXML(r io.Reader, obj interface{}) error { + decoder := xml.NewDecoder(r) if err := decoder.Decode(obj); err != nil { return err } diff --git a/context.go b/context.go index be205f45..9a51e461 100755 --- a/context.go +++ b/context.go @@ -31,6 +31,7 @@ const ( MIMEPlain = binding.MIMEPlain MIMEPOSTForm = binding.MIMEPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm + BodyBytesKey = "_gin-gonic/gin/bodybyteskey" ) const abortIndex int8 = math.MaxInt8 / 2 @@ -508,6 +509,30 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { return b.Bind(c.Request, obj) } +// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request +// body into the context, and reuse when it is called again. +// +// NOTE: This method reads the body before binding. So you should use +// ShouldBindWith for better performance if you need to call only once. +func (c *Context) ShouldBindBodyWith( + obj interface{}, bb binding.BindingBody, +) (err error) { + var body []byte + if cb, ok := c.Get(BodyBytesKey); ok { + if cbb, ok := cb.([]byte); ok { + body = cbb + } + } + if body == nil { + body, err = ioutil.ReadAll(c.Request.Body) + if err != nil { + return err + } + c.Set(BodyBytesKey, body) + } + return bb.BindBody(body, obj) +} + // ClientIP implements a best effort algorithm to return the real client IP, it parses // X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy. // Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP. diff --git a/context_test.go b/context_test.go index 841d8af0..e94866b6 100644 --- a/context_test.go +++ b/context_test.go @@ -18,6 +18,7 @@ import ( "time" "github.com/gin-contrib/sse" + "github.com/gin-gonic/gin/binding" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) @@ -1334,6 +1335,85 @@ func TestContextBadAutoShouldBind(t *testing.T) { assert.False(t, c.IsAborted()) } +func TestContextShouldBindBodyWith(t *testing.T) { + type typeA struct { + Foo string `json:"foo" xml:"foo" binding:"required"` + } + type typeB struct { + Bar string `json:"bar" xml:"bar" binding:"required"` + } + for _, tt := range []struct { + name string + bindingA, bindingB binding.BindingBody + bodyA, bodyB string + }{ + { + name: "JSON & JSON", + bindingA: binding.JSON, + bindingB: binding.JSON, + bodyA: `{"foo":"FOO"}`, + bodyB: `{"bar":"BAR"}`, + }, + { + name: "JSON & XML", + bindingA: binding.JSON, + bindingB: binding.XML, + bodyA: `{"foo":"FOO"}`, + bodyB: ` + + BAR +`, + }, + { + name: "XML & XML", + bindingA: binding.XML, + bindingB: binding.XML, + bodyA: ` + + FOO +`, + bodyB: ` + + BAR +`, + }, + } { + t.Logf("testing: %s", tt.name) + // bodyA to typeA and typeB + { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest( + "POST", "http://example.com", bytes.NewBufferString(tt.bodyA), + ) + // When it binds to typeA and typeB, it finds the body is + // not typeB but typeA. + objA := typeA{} + assert.NoError(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + assert.Equal(t, typeA{"FOO"}, objA) + objB := typeB{} + assert.Error(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + assert.NotEqual(t, typeB{"BAR"}, objB) + } + // bodyB to typeA and typeB + { + // When it binds to typeA and typeB, it finds the body is + // not typeA but typeB. + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + c.Request, _ = http.NewRequest( + "POST", "http://example.com", bytes.NewBufferString(tt.bodyB), + ) + objA := typeA{} + assert.Error(t, c.ShouldBindBodyWith(&objA, tt.bindingA)) + assert.NotEqual(t, typeA{"FOO"}, objA) + objB := typeB{} + assert.NoError(t, c.ShouldBindBodyWith(&objB, tt.bindingB)) + assert.Equal(t, typeB{"BAR"}, objB) + } + } +} + func TestContextGolangContext(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) From 5636afe02d00308bc5ade9dd80acfa7646b608df Mon Sep 17 00:00:00 2001 From: chainhelen Date: Fri, 11 May 2018 22:40:33 +0800 Subject: [PATCH 076/303] fix bug, return err when failed binding bool (#1350) * fix bug, return err when failed binding bool * add test, return err when failed binding bool --- binding/binding_test.go | 23 +++++++++++++++++++++++ binding/form_mapping.go | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index d6899dbb..936deac7 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -91,6 +91,10 @@ type FooStructForSliceMapType struct { SliceMapFoo []map[string]interface{} `form:"slice_map_foo"` } +type FooStructForBoolType struct { + BoolFoo bool `form:"bool_foo"` +} + type FooBarStructForIntType struct { IntFoo int `form:"int_foo"` IntBar int `form:"int_bar" binding:"required"` @@ -449,6 +453,12 @@ func TestBindingQueryFail2(t *testing.T) { "map_foo=unused", "") } +func TestBindingQueryBoolFail(t *testing.T) { + testQueryBindingBoolFail(t, "GET", + "/?bool_foo=fasl", "/?bar2=foo", + "bool_foo=unused", "") +} + func TestBindingXML(t *testing.T) { testBodyBinding(t, XML, "xml", @@ -1063,6 +1073,19 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str assert.Error(t, err) } +func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody string) { + b := Query + assert.Equal(t, "query", b.Name()) + + obj := FooStructForBoolType{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) +} + func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { assert.Equal(t, name, b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 6ff726df..3f6b9bfa 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -161,7 +161,7 @@ func setBoolField(val string, field reflect.Value) error { if err == nil { field.SetBool(boolVal) } - return nil + return err } func setFloatField(val string, bitSize int, field reflect.Value) error { From bf7803815b0baa22ff7a10457932882dfbf09925 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Lebreton Date: Sat, 12 May 2018 05:00:42 +0200 Subject: [PATCH 077/303] Serve easily dynamic files with `DataFromReader` context method (#1304) * Add DataFromReader context method * Replace fmt by strconv.FormatInt * Add pull request link to README --- README.md | 27 +++++++++++++++++++++++++++ context.go | 10 ++++++++++ context_test.go | 19 +++++++++++++++++++ render/reader.go | 36 ++++++++++++++++++++++++++++++++++++ render/render.go | 1 + render/render_test.go | 23 +++++++++++++++++++++++ 6 files changed, 116 insertions(+) create mode 100644 render/reader.go diff --git a/README.md b/README.md index 70a5a13b..51a807b5 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) - [JSONP rendering](#jsonp) - [Serving static files](#serving-static-files) + - [Serving data from reader](#serving-data-from-reader) - [HTML rendering](#html-rendering) - [Multitemplate](#multitemplate) - [Redirects](#redirects) @@ -901,6 +902,32 @@ func main() { } ``` +### Serving data from reader + +```go +func main() { + router := gin.Default() + router.GET("/someDataFromReader", func(c *gin.Context) { + response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png") + if err != nil || response.StatusCode != http.StatusOK { + c.Status(http.StatusServiceUnavailable) + return + } + + reader := response.Body + contentLength := response.ContentLength + contentType := response.Header.Get("Content-Type") + + extraHeaders := map[string]string{ + "Content-Disposition": `attachment; filename="gopher.png"`, + } + + c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) + }) + router.Run(":8080") +} +``` + ### HTML rendering Using LoadHTMLGlob() or LoadHTMLFiles() diff --git a/context.go b/context.go index 9a51e461..462357e1 100755 --- a/context.go +++ b/context.go @@ -741,6 +741,16 @@ func (c *Context) Data(code int, contentType string, data []byte) { }) } +// DataFromReader writes the specified reader into the body stream and updates the HTTP code. +func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) { + c.Render(code, render.Reader{ + Headers: extraHeaders, + ContentType: contentType, + ContentLength: contentLength, + Reader: reader, + }) +} + // File writes the specified file into the body stream in a efficient way. func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) diff --git a/context_test.go b/context_test.go index e94866b6..12e02fa0 100644 --- a/context_test.go +++ b/context_test.go @@ -1471,3 +1471,22 @@ func TestContextGetRawData(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "Fetch binary post data", string(data)) } + +func TestContextRenderDataFromReader(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + body := "#!PNG some raw data" + reader := strings.NewReader(body) + contentLength := int64(len(body)) + contentType := "image/png" + extraHeaders := map[string]string{"Content-Disposition": `attachment; filename="gopher.png"`} + + c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, body, w.Body.String()) + assert.Equal(t, contentType, w.HeaderMap.Get("Content-Type")) + assert.Equal(t, fmt.Sprintf("%d", contentLength), w.HeaderMap.Get("Content-Length")) + assert.Equal(t, extraHeaders["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) +} diff --git a/render/reader.go b/render/reader.go new file mode 100644 index 00000000..be2132c8 --- /dev/null +++ b/render/reader.go @@ -0,0 +1,36 @@ +package render + +import ( + "io" + "net/http" + "strconv" +) + +type Reader struct { + ContentType string + ContentLength int64 + Reader io.Reader + Headers map[string]string +} + +// Render (Reader) writes data with custom ContentType and headers. +func (r Reader) Render(w http.ResponseWriter) (err error) { + r.WriteContentType(w) + r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) + r.writeHeaders(w, r.Headers) + _, err = io.Copy(w, r.Reader) + return +} + +func (r Reader) WriteContentType(w http.ResponseWriter) { + writeContentType(w, []string{r.ContentType}) +} + +func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { + header := w.Header() + for k, v := range headers { + if val := header[k]; len(val) == 0 { + header[k] = []string{v} + } + } +} diff --git a/render/render.go b/render/render.go index 578f2784..7caf9bba 100755 --- a/render/render.go +++ b/render/render.go @@ -25,6 +25,7 @@ var ( _ HTMLRender = HTMLProduction{} _ Render = YAML{} _ Render = MsgPack{} + _ Render = Reader{} ) func writeContentType(w http.ResponseWriter, value []string) { diff --git a/render/render_test.go b/render/render_test.go index c7b51ef4..40ec806e 100755 --- a/render/render_test.go +++ b/render/render_test.go @@ -11,6 +11,8 @@ import ( "html/template" "net/http" "net/http/httptest" + "strconv" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -384,3 +386,24 @@ func TestRenderHTMLDebugPanics(t *testing.T) { } assert.Panics(t, func() { htmlRender.Instance("", nil) }) } + +func TestRenderReader(t *testing.T) { + w := httptest.NewRecorder() + + body := "#!PNG some raw data" + headers := make(map[string]string) + headers["Content-Disposition"] = `attachment; filename="filename.png"` + + err := (Reader{ + ContentLength: int64(len(body)), + ContentType: "image/png", + Reader: strings.NewReader(body), + Headers: headers, + }).Render(w) + + assert.NoError(t, err) + assert.Equal(t, body, w.Body.String()) + assert.Equal(t, "image/png", w.HeaderMap.Get("Content-Type")) + assert.Equal(t, strconv.Itoa(len(body)), w.HeaderMap.Get("Content-Length")) + assert.Equal(t, headers["Content-Disposition"], w.HeaderMap.Get("Content-Disposition")) +} From 07cbe116a058f13026fa6b23f675155968d47cc3 Mon Sep 17 00:00:00 2001 From: chainhelen Date: Wed, 30 May 2018 09:19:04 +0800 Subject: [PATCH 078/303] Add and fix the explanation of `HandleContext` (#1371) Reference this issue #1323 1. There isn't any eg about `HandleContext` 2. The `c.Request.Path` of `HandleContext` Comment is not right Based on the above two points, I pull this request. If you think it's unnecessary, I will close this. Thx. --- README.md | 16 ++++++++++++++-- gin.go | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 51a807b5..fd5c1755 100644 --- a/README.md +++ b/README.md @@ -1082,14 +1082,26 @@ Gin allow by default use only one html.Template. Check [a multitemplate render]( ### Redirects -Issuing a HTTP redirect is easy: +Issuing a HTTP redirect is easy. Both internal and external locations are supported. ```go r.GET("/test", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") }) ``` -Both internal and external locations are supported. + + +Issuing a Router redirect, use `HandleContext` like below. + +``` go +r.GET("/test", func(c *gin.Context) { + c.Request.URL.Path = "/test2" + r.HandleContext(c) +}) +r.GET("/test2", func(c *gin.Context) { + c.JSON(200, gin.H{"hello": "world"}) +}) +``` ### Custom Middleware diff --git a/gin.go b/gin.go index 4205eff0..b91fc2fa 100644 --- a/gin.go +++ b/gin.go @@ -329,7 +329,7 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // HandleContext re-enter a context that has been rewritten. -// This can be done by setting c.Request.Path to your new target. +// This can be done by setting c.Request.URL.Path to your new target. // Disclaimer: You can loop yourself to death with this, use wisely. func (engine *Engine) HandleContext(c *Context) { c.reset() From c2f083fc953073a5cfdcdd386fb3c7d1f004c2cc Mon Sep 17 00:00:00 2001 From: MW Lim Date: Thu, 31 May 2018 11:41:45 +0800 Subject: [PATCH 079/303] minor typo in routergroup.go (#1360) --- routergroup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 5e681c1c..53045d10 100644 --- a/routergroup.go +++ b/routergroup.go @@ -139,7 +139,7 @@ func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRou return group.returnObj() } -// StaticFile registers a single route in order to server a single file of the local filesystem. +// StaticFile registers a single route in order to serve a single file of the local filesystem. // router.StaticFile("favicon.ico", "./resources/favicon.ico") func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes { if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { From caf3e350a548af1add9def68087ac53d1d000caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 31 May 2018 14:13:40 +0800 Subject: [PATCH 080/303] doc: update readme for adding binding about skip validate (#1359) * update readme for adding binding about skip validate * update readme for adding binding about skip validate --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index fd5c1755..0bac65b2 100644 --- a/README.md +++ b/README.md @@ -572,6 +572,10 @@ $ curl -v -X POST \ {"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} ``` +**Skip validate** + +When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. + ### Custom Validators It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go). From 87d536c00120e6631cd7e6ff100739858e07e002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 21 Jun 2018 09:31:43 +0800 Subject: [PATCH 081/303] utils: use strings.Split instead of strings.IndexByte (#1400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And I test them benchmark: code: ```go # stringsbench.go package stringsbench import "strings" func index(part string) string { if index := strings.IndexByte(part, ';'); index >= 0 { if part := strings.TrimSpace(strings.Split(part, ";")[0]); part != "" { return part[0:index] } } return "" } func split(part string) string { return strings.Split(part, ";")[0] } ``` ```go # stringsbench_test.go package stringsbench import ( "testing" ) func BenchmarkIndex(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { index("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") } }) } func BenchmarkSplit(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { split("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8") } }) } ``` And the result: ```shell ➜ strings go test --bench=. goos: darwin goarch: amd64 BenchmarkIndex-8 30000000 46.1 ns/op BenchmarkSplit-8 50000000 35.9 ns/op PASS ok _/Users/tianou/strings 3.271s ➜ strings go test --bench=. goos: darwin goarch: amd64 BenchmarkIndex-8 30000000 44.2 ns/op BenchmarkSplit-8 50000000 34.7 ns/op PASS ok _/Users/tianou/strings 3.156s ➜ strings go test --bench=. goos: darwin goarch: amd64 BenchmarkIndex-8 30000000 45.6 ns/op BenchmarkSplit-8 50000000 35.3 ns/op PASS ok _/Users/tianou/strings 3.230s ``` --- utils.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/utils.go b/utils.go index 278029d7..bf32c775 100644 --- a/utils.go +++ b/utils.go @@ -103,10 +103,7 @@ func parseAccept(acceptHeader string) []string { parts := strings.Split(acceptHeader, ",") out := make([]string, 0, len(parts)) for _, part := range parts { - if index := strings.IndexByte(part, ';'); index >= 0 { - part = part[0:index] - } - if part = strings.TrimSpace(part); part != "" { + if part = strings.TrimSpace(strings.Split(part, ";")[0]); part != "" { out = append(out, part) } } From bf85b32c1d11dcfa2202daa6d1ad2addae2d1619 Mon Sep 17 00:00:00 2001 From: htobenothing Date: Thu, 21 Jun 2018 09:53:52 +0800 Subject: [PATCH 082/303] Add Pusher() function for support http2 server push (#1273) gin already support http2, while previously not support server push. Add Pusher() function to extend the ResponseWriter interface. ```golang // get http.Pusher if pusher := c.Writer.Pusher(); pusher != nil { // use pusher.Push() to do server push } ``` screen shot 2018-03-07 at 11 20 49 pm --- README.md | 50 ++++++++++++++++++++++++ examples/http-pusher/assets/app.js | 1 + examples/http-pusher/main.go | 41 +++++++++++++++++++ examples/http-pusher/testdata/ca.pem | 15 +++++++ examples/http-pusher/testdata/server.key | 16 ++++++++ examples/http-pusher/testdata/server.pem | 16 ++++++++ response_writer.go | 2 +- response_writer_1.7.go | 12 ++++++ response_writer_1.8.go | 25 ++++++++++++ 9 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 examples/http-pusher/assets/app.js create mode 100644 examples/http-pusher/main.go create mode 100644 examples/http-pusher/testdata/ca.pem create mode 100644 examples/http-pusher/testdata/server.key create mode 100644 examples/http-pusher/testdata/server.pem create mode 100644 response_writer_1.7.go create mode 100644 response_writer_1.8.go diff --git a/README.md b/README.md index 0bac65b2..c2917a5f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Build a single binary with templates](#build-a-single-binary-with-templates) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) + - [http2 server push](#http2-server-push) - [Testing](#testing) - [Users](#users--) @@ -1656,6 +1657,55 @@ enough to call binding at once. can be called by `c.ShouldBind()` multiple times without any damage to performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). +### http2 server push + +http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. + +[embedmd]:# (examples/http-pusher/main.go go) +```go +package main + +import ( + "html/template" + "log" + + "github.com/gin-gonic/gin" +) + +var html = template.Must(template.New("https").Parse(` + + + Https Test + + + +

Welcome, Ginner!

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

Welcome, Ginner!

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Hello world

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

Check some colors

- + - + - - + +
``` diff --git a/examples/realtime-advanced/resources/room_login.templ.html b/examples/realtime-advanced/resources/room_login.templ.html index 27dac387..5fb2b334 100644 --- a/examples/realtime-advanced/resources/room_login.templ.html +++ b/examples/realtime-advanced/resources/room_login.templ.html @@ -79,19 +79,19 @@
{{.nick}}
- +
- + {{else}}
Join the SSE real-time chat
- +
- +
{{end}} diff --git a/examples/realtime-chat/template.go b/examples/realtime-chat/template.go index b9024de6..4190251d 100644 --- a/examples/realtime-chat/template.go +++ b/examples/realtime-chat/template.go @@ -35,9 +35,9 @@ var html = template.Must(template.New("chat_room").Parse(`

Welcome to {{.roomid}} room

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

Welcome, Ginner!

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

Check some colors

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

- {{ .title }} -

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

- {{ .title }} -

-

Using posts/index.tmpl

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

- {{ .title }} -

-

Using users/index.tmpl

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

Welcome, Ginner!

- - -`)) - -func main() { - r := gin.Default() - r.Static("/assets", "./assets") - r.SetHTMLTemplate(html) - - r.GET("/", func(c *gin.Context) { - if pusher := c.Writer.Pusher(); pusher != nil { - // use pusher.Push() to do server push - if err := pusher.Push("/assets/app.js", nil); err != nil { - log.Printf("Failed to push: %v", err) - } - } - c.HTML(200, "https", gin.H{ - "status": "success", - }) - }) - - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") -} -``` - -### 定义路由日志的格式 - -默认的路由日志格式: -``` -[GIN-debug] POST /foo --> main.main.func1 (3 handlers) -[GIN-debug] GET /bar --> main.main.func2 (3 handlers) -[GIN-debug] GET /status --> main.main.func3 (3 handlers) -``` - -如果要以指的格式(例如JSON,Key Values或其他格式)记录信息,则可以使用`gin.DebugPrintRouteFunc`指定格式。 -在下面的示例中,我们使用标准日志包记录所有路由,但您可以使用其他满足需求的日志工具。 -```go -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { - log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) - } - - r.POST("/foo", func(c *gin.Context) { - c.JSON(http.StatusOK, "foo") - }) - - r.GET("/bar", func(c *gin.Context) { - c.JSON(http.StatusOK, "bar") - }) - - r.GET("/status", func(c *gin.Context) { - c.JSON(http.StatusOK, "ok") - }) - - // Listen and Server in http://0.0.0.0:8080 - r.Run() -} -``` -### 如何使用Cookie - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - - router.GET("/cookie", func(c *gin.Context) { - - cookie, err := c.Cookie("gin_cookie") - - if err != nil { - cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) - } - - fmt.Printf("Cookie value: %s \n", cookie) - }) - - router.Run() -} -``` - -## 测试 - -HTTP测试首选`net/http/httptest`包。 - -```go -package main - -func setupRouter() *gin.Engine { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - return r -} - -func main() { - r := setupRouter() - r.Run(":8080") -} -``` - -上面这段代码的测试用例: - -```go -package main - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPingRoute(t *testing.T) { - router := setupRouter() - - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/ping", nil) - router.ServeHTTP(w, req) - - assert.Equal(t, 200, w.Code) - assert.Equal(t, "pong", w.Body.String()) -} -``` - -## 用户 -使用[Gin](https://github.com/gin-gonic/gin)框架的著名项目: - -* [drone](https://github.com/drone/drone):用Go编写的基于docker的持续集成平台。 -* [gorush](https://github.com/appleboy/gorush):用Go编写的推送通知服务。 -* [fnproject](https://github.com/fnproject/fn):容器驱动、云无关的无服务器平台。 -* [photoprism](https://github.com/photoprism/photoprism): 用Go编写的基于TensorFlow的个人相册管理系统. -* [krakend](https://github.com/devopsfaith/krakend): 表现优异的API网关中间件。 From b97ccf3a43d2901d295870b5876277bf478a4909 Mon Sep 17 00:00:00 2001 From: MetalBreaker Date: Mon, 26 Nov 2018 16:01:51 +0100 Subject: [PATCH 190/303] Router: Route StaticFS() not found to Router's NoRoute() (#1663) Closes #1220 --- routergroup.go | 12 ++++++++++-- routes_test.go | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/routergroup.go b/routergroup.go index 9cb0b989..5615a50c 100644 --- a/routergroup.go +++ b/routergroup.go @@ -185,11 +185,19 @@ func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRou func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc { absolutePath := group.calculateAbsolutePath(relativePath) fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) - _, nolisting := fs.(*onlyfilesFS) + return func(c *Context) { - if nolisting { + file := c.Param("filepath") + + // Check if file exists and/or if we have permission to access it + if _, err := fs.Open(file); err != nil { c.Writer.WriteHeader(http.StatusNotFound) + c.handlers = group.engine.allNoRoute + // Reset index + c.index = -1 + return } + fileServer.ServeHTTP(c.Writer, c.Request) } } diff --git a/routes_test.go b/routes_test.go index 60f1c81b..c4d59725 100644 --- a/routes_test.go +++ b/routes_test.go @@ -411,6 +411,21 @@ func TestRouterNotFound(t *testing.T) { assert.Equal(t, http.StatusNotFound, w.Code) } +func TestRouterStaticFSNotFound(t *testing.T) { + router := New() + + router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/"))) + router.NoRoute(func(c *Context) { + c.String(404, "non existent") + }) + + w := performRequest(router, "GET", "/nonexistent") + assert.Equal(t, "non existent", w.Body.String()) + + w = performRequest(router, "HEAD", "/nonexistent") + assert.Equal(t, "non existent", w.Body.String()) +} + func TestRouteRawPath(t *testing.T) { route := New() route.UseRawPath = true From 54e9610400af963d321f8bdd91ce1507f7f8af9f Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 02:02:03 +0800 Subject: [PATCH 191/303] chore: remove wercker yml file (#1676) Now the `wercker.yml` have no longer used. --- wercker.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 wercker.yml diff --git a/wercker.yml b/wercker.yml deleted file mode 100644 index 3ab8084c..00000000 --- a/wercker.yml +++ /dev/null @@ -1 +0,0 @@ -box: wercker/default \ No newline at end of file From f463d847c23c85070653fe725cf27172633f57a0 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 05:58:35 +0800 Subject: [PATCH 192/303] chore: fix test fail (#1669) * chore: fix test fail * fix binduri test fail --- binding/binding_test.go | 2 +- recovery_test.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 5b311764..c0204d7f 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -687,7 +687,7 @@ func TestUriBinding(t *testing.T) { } var not NotSupportStruct assert.Error(t, b.BindUri(m, ¬)) - assert.Equal(t, "", not.Name) + assert.Equal(t, map[string]interface{}(nil), not.Name) } func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { diff --git a/recovery_test.go b/recovery_test.go index c9fb29ce..e886eaac 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -11,6 +11,7 @@ import ( "net" "net/http" "os" + "strings" "syscall" "testing" @@ -84,7 +85,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { const expectCode = 204 expectMsgs := map[syscall.Errno]string{ - syscall.EPIPE: "Broken pipe", + syscall.EPIPE: "broken pipe", syscall.ECONNRESET: "connection reset by peer", } @@ -108,7 +109,7 @@ func TestPanicWithBrokenPipe(t *testing.T) { w := performRequest(router, "GET", "/recovery") // TEST assert.Equal(t, expectCode, w.Code) - assert.Contains(t, buf.String(), expectMsg) + assert.Contains(t, strings.ToLower(buf.String()), expectMsg) }) } } From 98c7ac7202ffc7cfb60706bb48e0ef10f737abb1 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 13:36:49 +0800 Subject: [PATCH 193/303] fix bug (#1682) --- routergroup.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/routergroup.go b/routergroup.go index 5615a50c..2b41dfda 100644 --- a/routergroup.go +++ b/routergroup.go @@ -187,8 +187,11 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { - file := c.Param("filepath") + if _, nolisting := fs.(*onlyfilesFS); nolisting { + c.Writer.WriteHeader(http.StatusNotFound) + } + file := c.Param("filepath") // Check if file exists and/or if we have permission to access it if _, err := fs.Open(file); err != nil { c.Writer.WriteHeader(http.StatusNotFound) From cce49582d617333c72f4d86abd8ef0e62eb778a0 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 5 Dec 2018 13:49:03 +0800 Subject: [PATCH 194/303] ci: break when test fail (#1671) --- .gitignore | 2 ++ Makefile | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 14dc8f20..bdd50c95 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ vendor/* coverage.out count.out test +profile.out +tmp.out diff --git a/Makefile b/Makefile index b698ac09..b0d2e24a 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,12 @@ install: deps test: echo "mode: count" > coverage.out for d in $(TESTFOLDER); do \ - $(GO) test -v -covermode=count -coverprofile=profile.out $$d; \ + $(GO) test -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \ + cat tmp.out; \ + if grep -q "^--- FAIL" tmp.out; then \ + rm tmp.out; \ + exit 1;\ + fi; \ if [ -f profile.out ]; then \ cat profile.out | grep -v "mode:" >> coverage.out; \ rm profile.out; \ From f76ccb25f1eee8684e545ec00f8f2934fec98f09 Mon Sep 17 00:00:00 2001 From: Sai Date: Wed, 12 Dec 2018 10:05:16 +0900 Subject: [PATCH 195/303] 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 196/303] 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 197/303] 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 198/303] 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 199/303] 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 200/303] 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 201/303] 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 202/303] 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 203/303] 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 204/303] 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 205/303] 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 206/303] 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 207/303] 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 208/303] 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 209/303] 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 210/303] 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 211/303] 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 212/303] 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 213/303] 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 214/303] 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 215/303] 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 216/303] 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 217/303] 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 From 90587c7787a37e3b32375627717b936a17ab8e94 Mon Sep 17 00:00:00 2001 From: ffhelicopter <32922889+ffhelicopter@users.noreply.github.com> Date: Wed, 20 Feb 2019 13:24:29 +0800 Subject: [PATCH 218/303] Update: examples/graceful-shutdown/server.go (#1530) * Update server.go It's necessary that catching ctx.Done() * Update server.go * Update server.go * Update README.md * Update README.md --- README.md | 5 +++++ examples/graceful-shutdown/graceful-shutdown/server.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 3da8785b..90f6e1d1 100644 --- a/README.md +++ b/README.md @@ -1673,6 +1673,11 @@ func main() { if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } + // catching ctx.Done(). timeout of 5 seconds. + select { + case <-ctx.Done(): + log.Println("timeout of 5 seconds.") + } log.Println("Server exiting") } ``` diff --git a/examples/graceful-shutdown/graceful-shutdown/server.go b/examples/graceful-shutdown/graceful-shutdown/server.go index 33be0c8f..999a209e 100644 --- a/examples/graceful-shutdown/graceful-shutdown/server.go +++ b/examples/graceful-shutdown/graceful-shutdown/server.go @@ -48,5 +48,10 @@ func main() { if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } + // catching ctx.Done(). timeout of 5 seconds. + select { + case <-ctx.Done(): + log.Println("timeout of 5 seconds.") + } log.Println("Server exiting") } From a58a2f9bf368a7976d26dba4c16c90fad4c1db6e Mon Sep 17 00:00:00 2001 From: Olivier Robardet Date: Wed, 20 Feb 2019 14:14:16 +0100 Subject: [PATCH 219/303] Add a function to force color in console output (#1724) Add a function `ForceConsoleColor`, like `DisableConsoleColor` but to force coloring the output. It usefull when some IDE's integrated console (like IntelliJ or Goland) are not detected as TTY, but can display colors. Also helps if one want to output color in log file (#1590) and as a workaround for #1547. --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++--- logger.go | 10 ++++++++-- logger_test.go | 7 +++++++ 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 90f6e1d1..058eee2a 100644 --- a/README.md +++ b/README.md @@ -215,9 +215,6 @@ $ go build -tags=jsoniter . ```go func main() { - // Disable Console Color - // gin.DisableConsoleColor() - // Creates a gin router with default middleware: // logger and recovery (crash-free) middleware router := gin.Default() @@ -570,6 +567,48 @@ func main() { ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " ``` +### Controlling Log output coloring + +By default, logs output on console should be colorized depending on the detected TTY. + +Never colorize logs: + +```go +func main() { + // Disable log's color + gin.DisableConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +Always colorize logs: + +```go +func main() { + // Force log's color + gin.ForceConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + ### Model binding and validation To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). diff --git a/logger.go b/logger.go index d2869f51..6d8f838e 100644 --- a/logger.go +++ b/logger.go @@ -24,6 +24,7 @@ var ( cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) reset = string([]byte{27, 91, 48, 109}) disableColor = false + forceColor = false ) // LoggerConfig defines the config for Logger middleware. @@ -90,6 +91,11 @@ func DisableConsoleColor() { disableColor = true } +// ForceConsoleColor force color output in the console. +func ForceConsoleColor() { + forceColor = true +} + // ErrorLogger returns a handlerfunc for any error type. func ErrorLogger() HandlerFunc { return ErrorLoggerT(ErrorTypeAny) @@ -144,9 +150,9 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { isTerm := true - if w, ok := out.(*os.File); !ok || + if w, ok := out.(*os.File); (!ok || (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) || - disableColor { + disableColor) && !forceColor { isTerm = false } diff --git a/logger_test.go b/logger_test.go index d0169251..8770b5fb 100644 --- a/logger_test.go +++ b/logger_test.go @@ -340,3 +340,10 @@ func TestDisableConsoleColor(t *testing.T) { DisableConsoleColor() assert.True(t, disableColor) } + +func TestForceConsoleColor(t *testing.T) { + New() + assert.False(t, forceColor) + ForceConsoleColor() + assert.True(t, forceColor) +} From e6886e1539d89365f9970e1b5d9af1a2ccea16d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 21 Feb 2019 20:32:55 +0800 Subject: [PATCH 220/303] chore: fix Make script when failed (#1774) --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7211144a..7ab57e27 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,10 @@ test: exit 1; \ elif grep -q "build failed" tmp.out; then \ rm tmp.out; \ - exit; \ + exit 1; \ + elif grep -q "setup failed" tmp.out; then \ + rm tmp.out; \ + exit 1; \ fi; \ if [ -f profile.out ]; then \ cat profile.out | grep -v "mode:" >> coverage.out; \ From 4e86b17e734074af0317f9b0c40167f45fd0ca91 Mon Sep 17 00:00:00 2001 From: Mara Kim Date: Thu, 21 Feb 2019 22:45:32 -0500 Subject: [PATCH 221/303] Set socket to recieve writes (#1134) * Set socket to recieve writes * Update gin.go --- gin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gin.go b/gin.go index 6e5ea6d7..ad64c35f 100644 --- a/gin.go +++ b/gin.go @@ -318,6 +318,7 @@ func (engine *Engine) RunUnix(file string) (err error) { return } defer listener.Close() + os.Chmod(file, 0777) err = http.Serve(listener, engine) return } From 48f6c6137c9e326dc0aea110a43ad5a7a590073e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bazaglia?= Date: Fri, 22 Feb 2019 01:23:52 -0300 Subject: [PATCH 222/303] allow ignoring field on form mapping (#1733) --- binding/binding_test.go | 25 +++++++++++++++++++++++++ binding/form_mapping.go | 3 +++ 2 files changed, 28 insertions(+) diff --git a/binding/binding_test.go b/binding/binding_test.go index 1044e6c2..c9dea347 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -61,6 +61,10 @@ type FooStructForMapType struct { MapFoo map[string]interface{} `form:"map_foo"` } +type FooStructForIgnoreFormTag struct { + Foo *string `form:"-"` +} + type InvalidNameType struct { TestName string `invalid_name:"test_name"` } @@ -278,6 +282,12 @@ func TestBindingFormForTime2(t *testing.T) { "", "") } +func TestFormBindingIgnoreField(t *testing.T) { + testFormBindingIgnoreField(t, "POST", + "/", "/", + "-=bar", "") +} + func TestBindingFormInvalidName(t *testing.T) { testFormBindingInvalidName(t, "POST", "/", "/", @@ -860,6 +870,21 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod assert.Error(t, err) } +func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, "form", b.Name()) + + obj := FooStructForIgnoreFormTag{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + + assert.Nil(t, obj.Foo) +} + func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 8900ab70..8eb5c0d1 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -41,6 +41,9 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { defaultValue = defaultList[1] } } + if inputFieldName == "-" { + continue + } if inputFieldName == "" { inputFieldName = typeField.Name From d7daffc26b212514adb756e1ba807a6b8242dd37 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 22 Feb 2019 12:53:47 +0800 Subject: [PATCH 223/303] Use camel case instead of ALL_CAPS (#1419) * Use camel case instead of ALL_CAPS * Update mode.go --- mode.go | 6 +++--- mode_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mode.go b/mode.go index f787b5ca..8aa84aa8 100644 --- a/mode.go +++ b/mode.go @@ -11,8 +11,8 @@ import ( "github.com/gin-gonic/gin/binding" ) -// ENV_GIN_MODE indicates environment name for gin mode. -const ENV_GIN_MODE = "GIN_MODE" +// EnvGinMode indicates environment name for gin mode. +const EnvGinMode = "GIN_MODE" const ( // DebugMode indicates gin mode is debug. @@ -44,7 +44,7 @@ var ginMode = debugCode var modeName = DebugMode func init() { - mode := os.Getenv(ENV_GIN_MODE) + mode := os.Getenv(EnvGinMode) SetMode(mode) } diff --git a/mode_test.go b/mode_test.go index cf27acd8..3dba5150 100644 --- a/mode_test.go +++ b/mode_test.go @@ -13,13 +13,13 @@ import ( ) func init() { - os.Setenv(ENV_GIN_MODE, TestMode) + os.Setenv(EnvGinMode, TestMode) } func TestSetMode(t *testing.T) { assert.Equal(t, testCode, ginMode) assert.Equal(t, TestMode, Mode()) - os.Unsetenv(ENV_GIN_MODE) + os.Unsetenv(EnvGinMode) SetMode("") assert.Equal(t, debugCode, ginMode) From 184661cfa2f5ad6000c9893a0ffba8da8c1bd2ec Mon Sep 17 00:00:00 2001 From: Pierre Massat Date: Thu, 21 Feb 2019 21:12:05 -0800 Subject: [PATCH 224/303] Add response size to LogFormatterParams (#1752) --- logger.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/logger.go b/logger.go index 6d8f838e..bd28a11c 100644 --- a/logger.go +++ b/logger.go @@ -64,6 +64,8 @@ type LogFormatterParams struct { ErrorMessage string // IsTerm shows whether does gin's output descriptor refers to a terminal. IsTerm bool + // BodySize is the size of the Response Body + BodySize int } // defaultLogFormatter is the default log format function Logger middleware uses. @@ -191,6 +193,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { param.StatusCode = c.Writer.Status() param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() + param.BodySize = c.Writer.Size() + if raw != "" { path = path + "?" + raw } From 7b1081a73fe4559697f4fb5a44261fbdc1d130b2 Mon Sep 17 00:00:00 2001 From: songjiayang Date: Fri, 22 Feb 2019 14:20:24 +0800 Subject: [PATCH 225/303] issue_1721: fix render writeHeaders to make it the same as http.Header.Set (#1722) --- render/reader.go | 4 ++-- render/render_test.go | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/render/reader.go b/render/reader.go index ab60e53a..312af741 100644 --- a/render/reader.go +++ b/render/reader.go @@ -36,8 +36,8 @@ func (r Reader) WriteContentType(w http.ResponseWriter) { func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { header := w.Header() for k, v := range headers { - if val := header[k]; len(val) == 0 { - header[k] = []string{v} + if header.Get(k) == "" { + header.Set(k, v) } } } diff --git a/render/render_test.go b/render/render_test.go index 3df04a17..76e29eeb 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -470,6 +470,7 @@ func TestRenderReader(t *testing.T) { body := "#!PNG some raw data" headers := make(map[string]string) headers["Content-Disposition"] = `attachment; filename="filename.png"` + headers["x-request-id"] = "requestId" err := (Reader{ ContentLength: int64(len(body)), @@ -483,4 +484,5 @@ func TestRenderReader(t *testing.T) { assert.Equal(t, "image/png", w.Header().Get("Content-Type")) assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length")) assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) + assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } From e6288e90eb313122410bf1335d502a062c1ae610 Mon Sep 17 00:00:00 2001 From: Sai Date: Fri, 22 Feb 2019 17:48:55 +0900 Subject: [PATCH 226/303] Change color methods in using defaultLogger function to public (#1771) Fix https://github.com/gin-gonic/gin/issues/1768 --- logger.go | 85 ++++++++++++++++++++++++++++---------------------- logger_test.go | 19 +++++++++++ 2 files changed, 67 insertions(+), 37 deletions(-) diff --git a/logger.go b/logger.go index bd28a11c..dc639975 100644 --- a/logger.go +++ b/logger.go @@ -68,13 +68,58 @@ type LogFormatterParams struct { BodySize int } +// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. +func (p *LogFormatterParams) StatusCodeColor() string { + code := p.StatusCode + + switch { + case code >= http.StatusOK && code < http.StatusMultipleChoices: + return green + case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: + return white + case code >= http.StatusBadRequest && code < http.StatusInternalServerError: + return yellow + default: + return red + } +} + +// MethodColor is the ANSI color for appropriately logging http method to a terminal. +func (p *LogFormatterParams) MethodColor() string { + method := p.Method + + switch method { + case "GET": + return blue + case "POST": + return cyan + case "PUT": + return yellow + case "DELETE": + return red + case "PATCH": + return green + case "HEAD": + return magenta + case "OPTIONS": + return white + default: + return reset + } +} + +// ResetColor resets all escape attributes. +func (p *LogFormatterParams) ResetColor() string { + return reset +} + // defaultLogFormatter is the default log format function Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { var statusColor, methodColor, resetColor string if param.IsTerm { - statusColor = colorForStatus(param.StatusCode) - methodColor = colorForMethod(param.Method) - resetColor = reset + statusColor = param.StatusCodeColor() + methodColor = param.MethodColor() + resetColor = param.ResetColor() } return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", @@ -205,37 +250,3 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { } } } - -func colorForStatus(code int) string { - switch { - case code >= http.StatusOK && code < http.StatusMultipleChoices: - return green - case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: - return white - case code >= http.StatusBadRequest && code < http.StatusInternalServerError: - return yellow - default: - return red - } -} - -func colorForMethod(method string) string { - switch method { - case "GET": - return blue - case "POST": - return cyan - case "PUT": - return yellow - case "DELETE": - return red - case "PATCH": - return green - case "HEAD": - return magenta - case "OPTIONS": - return white - default: - return reset - } -} diff --git a/logger_test.go b/logger_test.go index 8770b5fb..c551677a 100644 --- a/logger_test.go +++ b/logger_test.go @@ -257,6 +257,13 @@ func TestDefaultLogFormatter(t *testing.T) { } func TestColorForMethod(t *testing.T) { + colorForMethod := func(method string) string { + p := LogFormatterParams{ + Method: method, + } + return p.MethodColor() + } + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") @@ -268,12 +275,24 @@ func TestColorForMethod(t *testing.T) { } func TestColorForStatus(t *testing.T) { + colorForStatus := func(code int) string { + p := LogFormatterParams{ + StatusCode: code, + } + return p.StatusCodeColor() + } + assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") } +func TestResetColor(t *testing.T) { + p := LogFormatterParams{} + assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor()) +} + func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) From d6adc8d0cc4e5a87e249923f511d093ffa552c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 26 Feb 2019 10:45:44 +0800 Subject: [PATCH 227/303] chore: add go1.12 support (#1780) * chore: add go1.12 support * Update .travis.yml --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2eeb0b3d..be196feb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ go: - 1.9.x - 1.10.x - 1.11.x + - 1.12.x - master matrix: @@ -14,6 +15,8 @@ matrix: include: - go: 1.11.x env: GO111MODULE=on + - go: 1.12.x + env: GO111MODULE=on git: depth: 10 From 62749f0db4aa5ee1045779b457fe1f28a553f467 Mon Sep 17 00:00:00 2001 From: Luis GG Date: Tue, 26 Feb 2019 01:15:40 -0300 Subject: [PATCH 228/303] Add context.HandlerNames() (#1729) * Add context.HandlerNames() This change adds a HandlerNames method that will return all registered handles in the context, in descending order This is useful for debugging and troubleshooting purposes, especially in large apps * Tests Add tests for HandlerNames * Fix HandlerNames test * Simplify test --- context.go | 10 ++++++++++ context_test.go | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/context.go b/context.go index 26badfc3..fa63ec8b 100644 --- a/context.go +++ b/context.go @@ -91,6 +91,16 @@ func (c *Context) HandlerName() string { return nameOfFunction(c.handlers.Last()) } +// HandlerNames returns a list of all registered handlers for this context in descending order, +// following the semantics of HandlerName() +func (c *Context) HandlerNames() []string { + hn := make([]string, 0, len(c.handlers)) + for _, val := range c.handlers { + hn = append(hn, nameOfFunction(val)) + } + return hn +} + // Handler returns the main handler. func (c *Context) Handler() HandlerFunc { return c.handlers.Last() diff --git a/context_test.go b/context_test.go index ea936b85..34cc71a5 100644 --- a/context_test.go +++ b/context_test.go @@ -340,10 +340,26 @@ func TestContextHandlerName(t *testing.T) { assert.Regexp(t, "^(.*/vendor/)?github.com/gin-gonic/gin.handlerNameTest$", c.HandlerName()) } +func TestContextHandlerNames(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest, func(c *Context) {}, handlerNameTest2} + + names := c.HandlerNames() + + assert.True(t, len(names) == 4) + for _, name := range names { + assert.Regexp(t, `^(.*/vendor/)?(github\.com/gin-gonic/gin\.){1}(TestContextHandlerNames\.func.*){0,1}(handlerNameTest.*){0,1}`, name) + } +} + func handlerNameTest(c *Context) { } +func handlerNameTest2(c *Context) { + +} + var handlerTest HandlerFunc = func(c *Context) { } From e207a3ce65323c3dda7cd8133fe14f4f1efd2500 Mon Sep 17 00:00:00 2001 From: Raphael Gavache Date: Tue, 26 Feb 2019 08:10:16 +0100 Subject: [PATCH 229/303] Fix context.Copy() race condition (#1020) * Fix context.Copy race condition * Update githubapi_test.go * fix code format --- context.go | 4 ++++ context_test.go | 2 ++ githubapi_test.go | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/context.go b/context.go index fa63ec8b..7618cef5 100644 --- a/context.go +++ b/context.go @@ -82,6 +82,10 @@ func (c *Context) Copy() *Context { cp.Writer = &cp.writermem cp.index = abortIndex cp.handlers = nil + cp.Keys = map[string]interface{}{} + for k, v := range c.Keys { + cp.Keys[k] = v + } return &cp } diff --git a/context_test.go b/context_test.go index 34cc71a5..dc8ac306 100644 --- a/context_test.go +++ b/context_test.go @@ -331,6 +331,8 @@ func TestContextCopy(t *testing.T) { assert.Equal(t, cp.Keys, c.Keys) assert.Equal(t, cp.engine, c.engine) assert.Equal(t, cp.Params, c.Params) + cp.Set("foo", "notBar") + assert.False(t, cp.Keys["foo"] == c.Keys["foo"]) } func TestContextHandlerName(t *testing.T) { diff --git a/githubapi_test.go b/githubapi_test.go index 29aa1584..fb74d659 100644 --- a/githubapi_test.go +++ b/githubapi_test.go @@ -346,6 +346,29 @@ func TestBindUriError(t *testing.T) { assert.Equal(t, http.StatusBadRequest, w1.Code) } +func TestRaceContextCopy(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + router.GET("/test/copy/race", func(c *Context) { + c.Set("1", 0) + c.Set("2", 0) + + // Sending a copy of the Context to two separate routines + go readWriteKeys(c.Copy()) + go readWriteKeys(c.Copy()) + c.String(http.StatusOK, "run OK, no panics") + }) + w := performRequest(router, "GET", "/test/copy/race") + assert.Equal(t, "run OK, no panics", w.Body.String()) +} + +func readWriteKeys(c *Context) { + for { + c.Set("1", rand.Int()) + c.Set("2", c.Value("1")) + } +} + func githubConfigRouter(router *Engine) { for _, route := range githubAPI { router.Handle(route.method, route.path, func(c *Context) { From ccb105dbcb48cbcec988df8a46e90af3adf7c0ff Mon Sep 17 00:00:00 2001 From: Tudor Roman Date: Wed, 27 Feb 2019 13:56:29 +0200 Subject: [PATCH 230/303] add prefix from X-Forwarded-Prefix in redirectTrailingSlash (#1238) * add prefix from X-Forwarded-Prefix in redirectTrailingSlash * added test * fix path import --- gin.go | 14 +++++++++----- routes_test.go | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/gin.go b/gin.go index ad64c35f..e28e9579 100644 --- a/gin.go +++ b/gin.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "os" + "path" "sync" "github.com/gin-gonic/gin/render" @@ -438,17 +439,20 @@ func serveError(c *Context, code int, defaultMessage []byte) { func redirectTrailingSlash(c *Context) { req := c.Request - path := req.URL.Path + p := req.URL.Path + if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." { + p = prefix + "/" + req.URL.Path + } code := http.StatusMovedPermanently // Permanent redirect, request with GET method if req.Method != "GET" { code = http.StatusTemporaryRedirect } - req.URL.Path = path + "/" - if length := len(path); length > 1 && path[length-1] == '/' { - req.URL.Path = path[:length-1] + req.URL.Path = p + "/" + if length := len(p); length > 1 && p[length-1] == '/' { + req.URL.Path = p[:length-1] } - debugPrint("redirecting request %d: %s --> %s", code, path, req.URL.String()) + debugPrint("redirecting request %d: %s --> %s", code, p, req.URL.String()) http.Redirect(c.Writer, req, req.URL.String(), code) c.writermem.WriteHeaderNow() } diff --git a/routes_test.go b/routes_test.go index 8d50292d..a842704f 100644 --- a/routes_test.go +++ b/routes_test.go @@ -16,8 +16,16 @@ import ( "github.com/stretchr/testify/assert" ) -func performRequest(r http.Handler, method, path string) *httptest.ResponseRecorder { +type header struct { + Key string + Value string +} + +func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder { req, _ := http.NewRequest(method, path, nil) + for _, h := range headers { + req.Header.Add(h.Key, h.Value) + } w := httptest.NewRecorder() r.ServeHTTP(w, req) return w @@ -170,6 +178,13 @@ func TestRouteRedirectTrailingSlash(t *testing.T) { w = performRequest(router, "PUT", "/path4/") assert.Equal(t, http.StatusOK, w.Code) + w = performRequest(router, "GET", "/path2", header{Key: "X-Forwarded-Prefix", Value: "/api"}) + assert.Equal(t, "/api/path2/", w.Header().Get("Location")) + assert.Equal(t, 301, w.Code) + + w = performRequest(router, "GET", "/path2/", header{Key: "X-Forwarded-Prefix", Value: "/api/"}) + assert.Equal(t, 200, w.Code) + router.RedirectTrailingSlash = false w = performRequest(router, "GET", "/path/") From 7dfa6c936a3f3169979f1bbfa074c6915f055881 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Thu, 28 Feb 2019 17:43:27 +0300 Subject: [PATCH 231/303] fix #1784: correct error comparison on tests (#1785) --- context_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index dc8ac306..060a8e8d 100644 --- a/context_test.go +++ b/context_test.go @@ -1242,22 +1242,24 @@ func TestContextError(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) assert.Empty(t, c.Errors) - c.Error(errors.New("first error")) // nolint: errcheck + firstErr := errors.New("first error") + c.Error(firstErr) // nolint: errcheck assert.Len(t, c.Errors, 1) assert.Equal(t, "Error #01: first error\n", c.Errors.String()) + secondErr := errors.New("second error") c.Error(&Error{ // nolint: errcheck - Err: errors.New("second error"), + Err: secondErr, Meta: "some data 2", Type: ErrorTypePublic, }) assert.Len(t, c.Errors, 2) - assert.Equal(t, errors.New("first error"), c.Errors[0].Err) + assert.Equal(t, firstErr, c.Errors[0].Err) assert.Nil(t, c.Errors[0].Meta) assert.Equal(t, ErrorTypePrivate, c.Errors[0].Type) - assert.Equal(t, errors.New("second error"), c.Errors[1].Err) + assert.Equal(t, secondErr, c.Errors[1].Err) assert.Equal(t, "some data 2", c.Errors[1].Meta) assert.Equal(t, ErrorTypePublic, c.Errors[1].Type) From 9bacadd3eab2e7271c456eedf2b02e4e09357d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 1 Mar 2019 07:11:02 +0800 Subject: [PATCH 232/303] remove docs dir (#1786) the post doc move https://gin-gonic.com/blog/ --- docs/how-to-build-an-effective-middleware.md | 137 ------------------- 1 file changed, 137 deletions(-) delete mode 100644 docs/how-to-build-an-effective-middleware.md diff --git a/docs/how-to-build-an-effective-middleware.md b/docs/how-to-build-an-effective-middleware.md deleted file mode 100644 index 568d5720..00000000 --- a/docs/how-to-build-an-effective-middleware.md +++ /dev/null @@ -1,137 +0,0 @@ -# How to build one effective middleware? - -## Consitituent part - -The middleware has two parts: - - - part one is what is executed once, when you initialize your middleware. That's where you set up all the global objects, logicals etc. Everything that happens one per application lifetime. - - - part two is what executes on every request. For example, a database middleware you simply inject your "global" database object into the context. Once it's inside the context, you can retrieve it from within other middlewares and your handler function. - -```go -func funcName(params string) gin.HandlerFunc { - // <--- - // This is part one - // ---> - // The follow code is an example - if err := check(params); err != nil { - panic(err) - } - - return func(c *gin.Context) { - // <--- - // This is part two - // ---> - // The follow code is an example - c.Set("TestVar", params) - c.Next() - } -} -``` - -## Execution process - -Firstly, we have the follow example code: - -```go -func main() { - router := gin.Default() - - router.Use(globalMiddleware()) - - router.GET("/rest/n/api/*some", mid1(), mid2(), handler) - - router.Run() -} - -func globalMiddleware() gin.HandlerFunc { - fmt.Println("globalMiddleware...1") - - return func(c *gin.Context) { - fmt.Println("globalMiddleware...2") - c.Next() - fmt.Println("globalMiddleware...3") - } -} - -func handler(c *gin.Context) { - fmt.Println("exec handler.") -} - -func mid1() gin.HandlerFunc { - fmt.Println("mid1...1") - - return func(c *gin.Context) { - - fmt.Println("mid1...2") - c.Next() - fmt.Println("mid1...3") - } -} - -func mid2() gin.HandlerFunc { - fmt.Println("mid2...1") - - return func(c *gin.Context) { - fmt.Println("mid2...2") - c.Next() - fmt.Println("mid2...3") - } -} -``` - -According to [Consitituent part](#consitituent-part) said, when we run the gin process, **part one** will execute firstly and will print the follow information: - -```go -globalMiddleware...1 -mid1...1 -mid2...1 -``` - -And init order are: - -```go -globalMiddleware...1 - | - v -mid1...1 - | - v -mid2...1 -``` - -When we curl one request `curl -v localhost:8080/rest/n/api/some`, **part two** will execute their middleware and output the following information: - -```go -globalMiddleware...2 -mid1...2 -mid2...2 -exec handler. -mid2...3 -mid1...3 -globalMiddleware...3 -``` - -In other words, run order are: - -```go -globalMiddleware...2 - | - v -mid1...2 - | - v -mid2...2 - | - v -exec handler. - | - v -mid2...3 - | - v -mid1...3 - | - v -globalMiddleware...3 -``` From 2dd31930060c978a84507571f7a3106afbf9673b Mon Sep 17 00:00:00 2001 From: Equim Date: Fri, 1 Mar 2019 10:03:14 +0800 Subject: [PATCH 233/303] Support negotiation wildcards, fix #391 (#1112) * support negotiation wildcards, fix #391 * fix typo --- context.go | 13 ++++++++++++- context_test.go | 28 ++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 7618cef5..08c420b1 100644 --- a/context.go +++ b/context.go @@ -952,7 +952,18 @@ func (c *Context) NegotiateFormat(offered ...string) string { } for _, accepted := range c.Accepted { for _, offert := range offered { - if accepted == offert { + // According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers, + // therefore we can just iterate over the string without casting it into []rune + i := 0 + for ; i < len(accepted); i++ { + if accepted[i] == '*' || offert[i] == '*' { + return offert + } + if accepted[i] != offert[i] { + break + } + } + if i == len(accepted) { return offert } } diff --git a/context_test.go b/context_test.go index 060a8e8d..483e1680 100644 --- a/context_test.go +++ b/context_test.go @@ -1158,17 +1158,41 @@ func TestContextNegotiationFormat(t *testing.T) { func TestContextNegotiationFormatWithAccept(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML)) assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML)) assert.Empty(t, c.NegotiateFormat(MIMEJSON)) } +func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("Accept", "*/*") + + assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") + assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") + assert.Equal(t, c.NegotiateFormat("application/*"), "application/*") + assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON) + assert.Equal(t, c.NegotiateFormat(MIMEXML), MIMEXML) + assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) + + c, _ = CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("Accept", "text/*") + + assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") + assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") + assert.Equal(t, c.NegotiateFormat("application/*"), "") + assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") + assert.Equal(t, c.NegotiateFormat(MIMEXML), "") + assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) +} + func TestContextNegotiationFormatCustom(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) c.Request, _ = http.NewRequest("POST", "/", nil) - c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") c.Accepted = nil c.SetAccepted(MIMEJSON, MIMEXML) From ccb9e902956d15e354867918b105e1595a2f370a Mon Sep 17 00:00:00 2001 From: Emmanuel Goh Date: Fri, 1 Mar 2019 10:17:47 +0800 Subject: [PATCH 234/303] Extend context.File to allow for the content-dispositon attachments via a new method context.Attachment (#1260) * Add FileAttachment method to context to allow instant downloads with filenames * Add relevant tests for FileAttachment method --- context.go | 8 ++++++++ context_test.go | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/context.go b/context.go index 08c420b1..e9735d28 100644 --- a/context.go +++ b/context.go @@ -6,6 +6,7 @@ package gin import ( "errors" + "fmt" "io" "io/ioutil" "math" @@ -880,6 +881,13 @@ func (c *Context) File(filepath string) { http.ServeFile(c.Writer, c.Request, filepath) } +// FileAttachment writes the specified file into the body stream in an efficient way +// On the client side, the file will typically be downloaded with the given filename +func (c *Context) FileAttachment(filepath, filename string) { + c.Writer.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) + http.ServeFile(c.Writer, c.Request, filepath) +} + // SSEvent writes a Server-Sent Event into the body stream. func (c *Context) SSEvent(name string, message interface{}) { c.Render(-1, sse.Event{ diff --git a/context_test.go b/context_test.go index 483e1680..0da5fbe6 100644 --- a/context_test.go +++ b/context_test.go @@ -979,6 +979,19 @@ func TestContextRenderFile(t *testing.T) { assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) } +func TestContextRenderAttachment(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + newFilename := "new_filename.go" + + c.Request, _ = http.NewRequest("GET", "/", nil) + c.FileAttachment("./gin.go", newFilename) + + assert.Equal(t, 200, w.Code) + assert.Contains(t, w.Body.String(), "func New() *Engine {") + assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.HeaderMap.Get("Content-Disposition")) +} + // TestContextRenderYAML tests that the response is serialized as YAML // and Content-Type is set to application/x-yaml func TestContextRenderYAML(t *testing.T) { From 0feaf8cbd80da13be634b13fd28bfb2d6e357839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Fri, 1 Mar 2019 23:42:41 +0800 Subject: [PATCH 235/303] Split examples to alone repo (#1776) * split examples to alone repo * vendor * fix package error * add examples/README.md --- .travis.yml | 1 - Makefile | 9 +- README.md | 6 - examples/README.md | 3 + examples/app-engine/README.md | 8 - examples/app-engine/app.yaml | 8 - examples/app-engine/hello.go | 24 -- examples/assets-in-binary/README.md | 33 --- examples/assets-in-binary/assets.go | 34 --- examples/assets-in-binary/html/bar.tmpl | 4 - examples/assets-in-binary/html/index.tmpl | 4 - examples/assets-in-binary/main.go | 48 ---- examples/auto-tls/example1/main.go | 19 -- examples/auto-tls/example2/main.go | 26 --- examples/basic/main.go | 65 ------ examples/basic/main_test.go | 20 -- examples/custom-validation/server.go | 50 ----- examples/favicon/favicon.ico | Bin 1150 -> 0 bytes examples/favicon/main.go | 17 -- examples/graceful-shutdown/close/server.go | 45 ---- .../graceful-shutdown/server.go | 57 ----- examples/grpc/README.md | 19 -- examples/grpc/gin/main.go | 46 ---- examples/grpc/grpc/server.go | 34 --- examples/grpc/pb/helloworld.pb.go | 151 ------------- examples/grpc/pb/helloworld.proto | 37 ---- examples/http-pusher/assets/app.js | 1 - examples/http-pusher/main.go | 41 ---- examples/http-pusher/testdata/ca.pem | 15 -- examples/http-pusher/testdata/server.key | 16 -- examples/http-pusher/testdata/server.pem | 16 -- examples/http2/README.md | 18 -- examples/http2/main.go | 38 ---- examples/http2/testdata/ca.pem | 15 -- examples/http2/testdata/server.key | 16 -- examples/http2/testdata/server.pem | 16 -- examples/multiple-service/main.go | 74 ------- examples/new_relic/README.md | 30 --- examples/new_relic/main.go | 42 ---- examples/realtime-advanced/Makefile | 10 - examples/realtime-advanced/main.go | 42 ---- .../resources/room_login.templ.html | 208 ------------------ .../resources/static/epoch.min.css | 1 - .../resources/static/epoch.min.js | 114 ---------- .../resources/static/prismjs.min.css | 137 ------------ .../resources/static/prismjs.min.js | 5 - .../resources/static/realtime.js | 144 ------------ examples/realtime-advanced/rooms.go | 25 --- examples/realtime-advanced/routes.go | 96 -------- examples/realtime-advanced/stats.go | 59 ----- examples/realtime-chat/Makefile | 9 - examples/realtime-chat/main.go | 59 ----- examples/realtime-chat/rooms.go | 33 --- examples/realtime-chat/template.go | 44 ---- examples/struct-lvl-validations/README.md | 50 ----- examples/struct-lvl-validations/server.go | 64 ------ examples/template/main.go | 32 --- examples/upload-file/multiple/main.go | 39 ---- .../upload-file/multiple/public/index.html | 17 -- examples/upload-file/single/main.go | 36 --- examples/upload-file/single/public/index.html | 16 -- go.mod | 24 +- go.sum | 42 +--- vendor/vendor.json | 6 + 64 files changed, 25 insertions(+), 2393 deletions(-) create mode 100644 examples/README.md delete mode 100644 examples/app-engine/README.md delete mode 100644 examples/app-engine/app.yaml delete mode 100644 examples/app-engine/hello.go delete mode 100644 examples/assets-in-binary/README.md delete mode 100644 examples/assets-in-binary/assets.go delete mode 100644 examples/assets-in-binary/html/bar.tmpl delete mode 100644 examples/assets-in-binary/html/index.tmpl delete mode 100644 examples/assets-in-binary/main.go delete mode 100644 examples/auto-tls/example1/main.go delete mode 100644 examples/auto-tls/example2/main.go delete mode 100644 examples/basic/main.go delete mode 100644 examples/basic/main_test.go delete mode 100644 examples/custom-validation/server.go delete mode 100644 examples/favicon/favicon.ico delete mode 100644 examples/favicon/main.go delete mode 100644 examples/graceful-shutdown/close/server.go delete mode 100644 examples/graceful-shutdown/graceful-shutdown/server.go delete mode 100644 examples/grpc/README.md delete mode 100644 examples/grpc/gin/main.go delete mode 100644 examples/grpc/grpc/server.go delete mode 100644 examples/grpc/pb/helloworld.pb.go delete mode 100644 examples/grpc/pb/helloworld.proto delete mode 100644 examples/http-pusher/assets/app.js delete mode 100644 examples/http-pusher/main.go delete mode 100644 examples/http-pusher/testdata/ca.pem delete mode 100644 examples/http-pusher/testdata/server.key delete mode 100644 examples/http-pusher/testdata/server.pem delete mode 100644 examples/http2/README.md delete mode 100644 examples/http2/main.go delete mode 100644 examples/http2/testdata/ca.pem delete mode 100644 examples/http2/testdata/server.key delete mode 100644 examples/http2/testdata/server.pem delete mode 100644 examples/multiple-service/main.go delete mode 100644 examples/new_relic/README.md delete mode 100644 examples/new_relic/main.go delete mode 100644 examples/realtime-advanced/Makefile delete mode 100644 examples/realtime-advanced/main.go delete mode 100644 examples/realtime-advanced/resources/room_login.templ.html delete mode 100644 examples/realtime-advanced/resources/static/epoch.min.css delete mode 100644 examples/realtime-advanced/resources/static/epoch.min.js delete mode 100644 examples/realtime-advanced/resources/static/prismjs.min.css delete mode 100644 examples/realtime-advanced/resources/static/prismjs.min.js delete mode 100644 examples/realtime-advanced/resources/static/realtime.js delete mode 100644 examples/realtime-advanced/rooms.go delete mode 100644 examples/realtime-advanced/routes.go delete mode 100644 examples/realtime-advanced/stats.go delete mode 100644 examples/realtime-chat/Makefile delete mode 100644 examples/realtime-chat/main.go delete mode 100644 examples/realtime-chat/rooms.go delete mode 100644 examples/realtime-chat/template.go delete mode 100644 examples/struct-lvl-validations/README.md delete mode 100644 examples/struct-lvl-validations/server.go delete mode 100644 examples/template/main.go delete mode 100644 examples/upload-file/multiple/main.go delete mode 100644 examples/upload-file/multiple/public/index.html delete mode 100644 examples/upload-file/single/main.go delete mode 100644 examples/upload-file/single/public/index.html diff --git a/.travis.yml b/.travis.yml index be196feb..00393750 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,6 @@ go_import_path: github.com/gin-gonic/gin script: - make vet - make fmt-check - - make embedmd - make misspell-check - make test diff --git a/Makefile b/Makefile index 7ab57e27..51a6b916 100644 --- a/Makefile +++ b/Makefile @@ -52,12 +52,6 @@ deps: @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u github.com/kardianos/govendor; \ fi - @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/campoy/embedmd; \ - fi - -embedmd: - embedmd -d *.md .PHONY: lint lint: @@ -83,5 +77,4 @@ misspell: .PHONY: tools tools: go install golang.org/x/lint/golint; \ - go install github.com/client9/misspell/cmd/misspell; \ - go install github.com/campoy/embedmd; + go install github.com/client9/misspell/cmd/misspell; diff --git a/README.md b/README.md index 058eee2a..a4ced64e 100644 --- a/README.md +++ b/README.md @@ -728,7 +728,6 @@ When running the above example using the above the `curl` command, it returns er It is also possible to register custom validators. See the [example code](examples/custom-validation/server.go). -[embedmd]:# (examples/custom-validation/server.go go) ```go package main @@ -1501,7 +1500,6 @@ func main() { example for 1-line LetsEncrypt HTTPS servers. -[embedmd]:# (examples/auto-tls/example1/main.go go) ```go package main @@ -1526,7 +1524,6 @@ func main() { example for custom autocert manager. -[embedmd]:# (examples/auto-tls/example2/main.go go) ```go package main @@ -1560,7 +1557,6 @@ func main() { See the [question](https://github.com/gin-gonic/gin/issues/346) and try the following example: -[embedmd]:# (examples/multiple-service/main.go go) ```go package main @@ -1660,7 +1656,6 @@ An alternative to endless: If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](./examples/graceful-shutdown) example with gin. -[embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) ```go // +build go1.8 @@ -1919,7 +1914,6 @@ performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)). http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information. -[embedmd]:# (examples/http-pusher/main.go go) ```go package main diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..4b3b718c --- /dev/null +++ b/examples/README.md @@ -0,0 +1,3 @@ +# Gin Examples + +## TODO diff --git a/examples/app-engine/README.md b/examples/app-engine/README.md deleted file mode 100644 index b3dd7c78..00000000 --- a/examples/app-engine/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Guide to run Gin under App Engine LOCAL Development Server - -1. Download, install and setup Go in your computer. (That includes setting your `$GOPATH`.) -2. Download SDK for your platform from [here](https://cloud.google.com/appengine/docs/standard/go/download): `https://cloud.google.com/appengine/docs/standard/go/download` -3. Download Gin source code using: `$ go get github.com/gin-gonic/gin` -4. Navigate to examples folder: `$ cd $GOPATH/src/github.com/gin-gonic/gin/examples/app-engine/` -5. Run it: `$ dev_appserver.py .` (notice that you have to run this script by Python2) - diff --git a/examples/app-engine/app.yaml b/examples/app-engine/app.yaml deleted file mode 100644 index 5f20cf3f..00000000 --- a/examples/app-engine/app.yaml +++ /dev/null @@ -1,8 +0,0 @@ -application: hello -version: 1 -runtime: go -api_version: go1 - -handlers: -- url: /.* - script: _go_app \ No newline at end of file diff --git a/examples/app-engine/hello.go b/examples/app-engine/hello.go deleted file mode 100644 index f569dadb..00000000 --- a/examples/app-engine/hello.go +++ /dev/null @@ -1,24 +0,0 @@ -package hello - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -// This function's name is a must. App Engine uses it to drive the requests properly. -func init() { - // Starts a new Gin instance with no middle-ware - r := gin.New() - - // Define your handlers - r.GET("/", func(c *gin.Context) { - c.String(http.StatusOK, "Hello World!") - }) - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "pong") - }) - - // Handle all requests using net/http - http.Handle("/", r) -} diff --git a/examples/assets-in-binary/README.md b/examples/assets-in-binary/README.md deleted file mode 100644 index 0c23bb0d..00000000 --- a/examples/assets-in-binary/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Building a single binary containing templates - -This is a complete example to create a single binary with the -[gin-gonic/gin][gin] Web Server with HTML templates. - -[gin]: https://github.com/gin-gonic/gin - -## How to use - -### Prepare Packages - -``` -go get github.com/gin-gonic/gin -go get github.com/jessevdk/go-assets-builder -``` - -### Generate assets.go - -``` -go-assets-builder html -o assets.go -``` - -### Build the server - -``` -go build -o assets-in-binary -``` - -### Run - -``` -./assets-in-binary -``` diff --git a/examples/assets-in-binary/assets.go b/examples/assets-in-binary/assets.go deleted file mode 100644 index dcc5c465..00000000 --- a/examples/assets-in-binary/assets.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "time" - - "github.com/jessevdk/go-assets" -) - -var _Assetsbfa8d115ce0617d89507412d5393a462f8e9b003 = "\n\n

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

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

Hello, {{.Foo}}

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

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

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

Hello, {{.Foo}}

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

Welcome, Ginner!

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

Welcome, Ginner!

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

Server-Sent Events in Go

-

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

-

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

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

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

-
-
-
-
-
-
-

Realtime server Go stats

-
-

Memory usage

-

-

-

-

- ◼︎ Heap bytes
- ◼︎ Stack bytes
-

-
-
-

Allocations per second

-

-

-

-

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

-
-
-
-

MIT Open Sourced

- -
- -

Server-side (Go)

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

Client-side (JS)

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

SSE package

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

Welcome to {{.roomid}} room

-
-
- User: - Message: - -
- - -`)) diff --git a/examples/struct-lvl-validations/README.md b/examples/struct-lvl-validations/README.md deleted file mode 100644 index 1bd57f03..00000000 --- a/examples/struct-lvl-validations/README.md +++ /dev/null @@ -1,50 +0,0 @@ -## Struct level validations - -Validations can also be registered at the `struct` level when field level validations -don't make much sense. This can also be used to solve cross-field validation elegantly. -Additionally, it can be combined with tag validations. Struct Level validations run after -the structs tag validations. - -### Example requests - -```shell -# Validation errors are generated for struct tags as well as at the struct level -$ curl -s -X POST http://localhost:8085/user \ - -H 'content-type: application/json' \ - -d '{}' | jq -{ - "error": "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag\nKey: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", - "message": "User validation failed!" -} - -# Validation fails at the struct level because neither first name nor last name are present -$ curl -s -X POST http://localhost:8085/user \ - -H 'content-type: application/json' \ - -d '{"email": "george@vandaley.com"}' | jq -{ - "error": "Key: 'User.FirstName' Error:Field validation for 'FirstName' failed on the 'fnameorlname' tag\nKey: 'User.LastName' Error:Field validation for 'LastName' failed on the 'fnameorlname' tag", - "message": "User validation failed!" -} - -# No validation errors when either first name or last name is present -$ curl -X POST http://localhost:8085/user \ - -H 'content-type: application/json' \ - -d '{"fname": "George", "email": "george@vandaley.com"}' -{"message":"User validation successful."} - -$ curl -X POST http://localhost:8085/user \ - -H 'content-type: application/json' \ - -d '{"lname": "Contanza", "email": "george@vandaley.com"}' -{"message":"User validation successful."} - -$ curl -X POST http://localhost:8085/user \ - -H 'content-type: application/json' \ - -d '{"fname": "George", "lname": "Costanza", "email": "george@vandaley.com"}' -{"message":"User validation successful."} -``` - -### Useful links - -- Validator docs - https://godoc.org/gopkg.in/go-playground/validator.v8#Validate.RegisterStructValidation -- Struct level example - https://github.com/go-playground/validator/blob/v8.18.2/examples/struct-level/struct_level.go -- Validator release notes - https://github.com/go-playground/validator/releases/tag/v8.7 diff --git a/examples/struct-lvl-validations/server.go b/examples/struct-lvl-validations/server.go deleted file mode 100644 index be807b78..00000000 --- a/examples/struct-lvl-validations/server.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "net/http" - "reflect" - - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" - validator "gopkg.in/go-playground/validator.v8" -) - -// User contains user information. -type User struct { - FirstName string `json:"fname"` - LastName string `json:"lname"` - Email string `binding:"required,email"` -} - -// UserStructLevelValidation contains custom struct level validations that don't always -// make sense at the field validation level. For example, this function validates that either -// FirstName or LastName exist; could have done that with a custom field validation but then -// would have had to add it to both fields duplicating the logic + overhead, this way it's -// only validated once. -// -// NOTE: you may ask why wouldn't not just do this outside of validator. Doing this way -// hooks right into validator and you can combine with validation tags and still have a -// common error output format. -func UserStructLevelValidation(v *validator.Validate, structLevel *validator.StructLevel) { - user := structLevel.CurrentStruct.Interface().(User) - - if len(user.FirstName) == 0 && len(user.LastName) == 0 { - structLevel.ReportError( - reflect.ValueOf(user.FirstName), "FirstName", "fname", "fnameorlname", - ) - structLevel.ReportError( - reflect.ValueOf(user.LastName), "LastName", "lname", "fnameorlname", - ) - } - - // plus can to more, even with different tag than "fnameorlname" -} - -func main() { - route := gin.Default() - - if v, ok := binding.Validator.Engine().(*validator.Validate); ok { - v.RegisterStructValidation(UserStructLevelValidation, User{}) - } - - route.POST("/user", validateUser) - route.Run(":8085") -} - -func validateUser(c *gin.Context) { - var u User - if err := c.ShouldBindJSON(&u); err == nil { - c.JSON(http.StatusOK, gin.H{"message": "User validation successful."}) - } else { - c.JSON(http.StatusBadRequest, gin.H{ - "message": "User validation failed!", - "error": err.Error(), - }) - } -} diff --git a/examples/template/main.go b/examples/template/main.go deleted file mode 100644 index e20a3b98..00000000 --- a/examples/template/main.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "fmt" - "html/template" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -func formatAsDate(t time.Time) string { - year, month, day := t.Date() - return fmt.Sprintf("%d%02d/%02d", year, month, day) -} - -func main() { - router := gin.Default() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("../../testdata/template/raw.tmpl") - - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - - router.Run(":8080") -} diff --git a/examples/upload-file/multiple/main.go b/examples/upload-file/multiple/main.go deleted file mode 100644 index 2b9d6d91..00000000 --- a/examples/upload-file/multiple/main.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "path/filepath" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - // Set a lower memory limit for multipart forms (default is 32 MiB) - router.MaxMultipartMemory = 8 << 20 // 8 MiB - router.Static("/", "./public") - router.POST("/upload", func(c *gin.Context) { - name := c.PostForm("name") - email := c.PostForm("email") - - // Multipart form - form, err := c.MultipartForm() - if err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error())) - return - } - files := form.File["files"] - - for _, file := range files { - filename := filepath.Base(file.Filename) - if err := c.SaveUploadedFile(file, filename); err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error())) - return - } - } - - c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email)) - }) - router.Run(":8080") -} diff --git a/examples/upload-file/multiple/public/index.html b/examples/upload-file/multiple/public/index.html deleted file mode 100644 index b8463601..00000000 --- a/examples/upload-file/multiple/public/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Multiple file upload - - -

Upload multiple files with fields

- -
- Name:
- Email:
- Files:

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

Upload single file with fields

- -
- Name:
- Email:
- Files:

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

Check some colors

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

- {{ .title }} -

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

- {{ .title }} -

-

Using posts/index.tmpl

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

- {{ .title }} -

-

Using users/index.tmpl

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

Welcome, Ginner!

- - -`)) - -func main() { - r := gin.Default() - r.Static("/assets", "./assets") - r.SetHTMLTemplate(html) - - r.GET("/", func(c *gin.Context) { - if pusher := c.Writer.Pusher(); pusher != nil { - // use pusher.Push() to do server push - if err := pusher.Push("/assets/app.js", nil); err != nil { - log.Printf("Failed to push: %v", err) - } - } - c.HTML(200, "https", gin.H{ - "status": "success", - }) - }) - - // Listen and Server in https://127.0.0.1:8080 - r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key") -} -``` - -### Define format for the log of routes - -The default log of routes is: -``` -[GIN-debug] POST /foo --> main.main.func1 (3 handlers) -[GIN-debug] GET /bar --> main.main.func2 (3 handlers) -[GIN-debug] GET /status --> main.main.func3 (3 handlers) -``` - -If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`. -In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs. -```go -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { - log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers) - } - - r.POST("/foo", func(c *gin.Context) { - c.JSON(http.StatusOK, "foo") - }) - - r.GET("/bar", func(c *gin.Context) { - c.JSON(http.StatusOK, "bar") - }) - - r.GET("/status", func(c *gin.Context) { - c.JSON(http.StatusOK, "ok") - }) - - // Listen and Server in http://0.0.0.0:8080 - r.Run() -} -``` - -### Set and get a cookie - -```go -import ( - "fmt" - - "github.com/gin-gonic/gin" -) - -func main() { - - router := gin.Default() - - router.GET("/cookie", func(c *gin.Context) { - - cookie, err := c.Cookie("gin_cookie") - - if err != nil { - cookie = "NotSet" - c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) - } - - fmt.Printf("Cookie value: %s \n", cookie) - }) - - router.Run() -} -``` - - -## Testing - -The `net/http/httptest` package is preferable way for HTTP testing. - -```go -package main - -func setupRouter() *gin.Engine { - r := gin.Default() - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - return r -} - -func main() { - r := setupRouter() - r.Run(":8080") -} -``` - -Test for code example above: - -```go -package main - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPingRoute(t *testing.T) { - router := setupRouter() - - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/ping", nil) - router.ServeHTTP(w, req) - - assert.Equal(t, 200, w.Code) - assert.Equal(t, "pong", w.Body.String()) -} -``` +A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. ## Users -Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. +[Gin website](https://gin-gonic.com/docs/users/) lists some awesome projects made with Gin web framework. + +## Contributing + +Gin is the work of hundreds of contributors. We appreciate your help! + +Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow. -* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. -* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform. -* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. -* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. -* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. From 483f828bce15ff82fdec7414c32ee9b3f017b5ca Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Thu, 14 Mar 2019 08:34:56 +0300 Subject: [PATCH 252/303] add support arrays on mapping (#1797) * add support arrays on mapping * not allow default value on array mapping --- binding/binding_test.go | 28 ++++++++++++++++++++++++++++ binding/form_mapping.go | 23 ++++++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 5ae87957..b265af36 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -1430,3 +1430,31 @@ func TestBindingTimeDuration(t *testing.T) { err = Form.Bind(req, &s) assert.Error(t, err) } + +func TestBindingArray(t *testing.T) { + var s struct { + Nums [2]int `form:"nums,default=4"` + } + + // default + req := formPostRequest("", "") + err := Form.Bind(req, &s) + assert.Error(t, err) + assert.Equal(t, [2]int{0, 0}, s.Nums) + + // ok + req = formPostRequest("", "nums=3&nums=8") + err = Form.Bind(req, &s) + assert.NoError(t, err) + assert.Equal(t, [2]int{3, 8}, s.Nums) + + // not enough vals + req = formPostRequest("", "nums=3") + err = Form.Bind(req, &s) + assert.Error(t, err) + + // error + req = formPostRequest("", "nums=3&nums=wrong") + err = Form.Bind(req, &s) + assert.Error(t, err) +} diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 87edfbb2..ba9d2c4f 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -6,6 +6,7 @@ package binding import ( "errors" + "fmt" "reflect" "strconv" "strings" @@ -118,6 +119,14 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri vs = []string{defaultValue} } return true, setSlice(vs, value, field) + case reflect.Array: + if !ok { + vs = []string{defaultValue} + } + if len(vs) != value.Len() { + return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) + } + return true, setArray(vs, value, field) default: var val string if !ok { @@ -256,14 +265,22 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val return nil } -func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { - slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) +func setArray(vals []string, value reflect.Value, field reflect.StructField) error { for i, s := range vals { - err := setWithProperType(s, slice.Index(i), field) + err := setWithProperType(s, value.Index(i), field) if err != nil { return err } } + return nil +} + +func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { + slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) + err := setArray(vals, slice, field) + if err != nil { + return err + } value.Set(slice) return nil } From 242a2622c839bf883c415fd088d24fec8727fb23 Mon Sep 17 00:00:00 2001 From: Sai Date: Thu, 14 Mar 2019 17:26:51 +0900 Subject: [PATCH 253/303] Fix Japanese text hiragana -> kanji (#1812) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9c10b22..b46c5637 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ All documentation is available on the Gin website. - [English](https://gin-gonic.com/docs/) - [简体中文](https://gin-gonic.com/zh-cn/docs/) - [繁體中文](https://gin-gonic.com/zh-tw/docs/) -- [にほんご](https://gin-gonic.com/ja/docs/) +- [日本語](https://gin-gonic.com/ja/docs/) ## Examples From 05b5c3ba7495fb3cd737cad8b35e62e0862ed1c2 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Fri, 15 Mar 2019 15:39:34 +0800 Subject: [PATCH 254/303] Doc: fix gin example notice syntax (#1814) --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index b02deae4..bfebc6c0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,3 @@ # Gin Examples -⚠️ **NOTICE:** All gin examples has moved as alone repository to [here](https://github.com/gin-gonic/examples). +⚠️ **NOTICE:** All gin examples have been moved as standalone repository to [here](https://github.com/gin-gonic/examples). From bcf36ade9f763b875bd781ad26cdb0549349c5f8 Mon Sep 17 00:00:00 2001 From: sekky0905 <20237968+sekky0905@users.noreply.github.com> Date: Sat, 16 Mar 2019 17:09:10 +0900 Subject: [PATCH 255/303] Remove sudo setting from travis.yml (#1816) --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b38adcb1..2fd9c8a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: go -sudo: false matrix: fast_finish: true From c16bfa7949c6ca59c049d20df507d24b1f2ec629 Mon Sep 17 00:00:00 2001 From: Boyi Wu Date: Mon, 18 Mar 2019 10:16:34 +0800 Subject: [PATCH 256/303] update for supporting file binding (#1264) update for supporting multipart form and file binding example: ``` type PhoptUploadForm struct { imgData *multipart.FileHeader `form:"img_data" binding:"required"` ProjectID string `form:"project_id" binding:"required"` Description string `form:"description binding:"required"` } ``` ref: https://github.com/gin-gonic/gin/issues/1263 --- binding/binding.go | 4 +- binding/binding_test.go | 94 ++++++++++++++++++++++++++++++++++++++++- binding/form.go | 5 +++ binding/form_mapping.go | 29 +++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) diff --git a/binding/binding.go b/binding/binding.go index 26d71c9f..520c5109 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -98,7 +98,9 @@ func Default(method, contentType string) Binding { return MsgPack case MIMEYAML: return YAML - default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: + case MIMEMultipartPOSTForm: + return FormMultipart + default: // case MIMEPOSTForm: return Form } } diff --git a/binding/binding_test.go b/binding/binding_test.go index b265af36..ee788225 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -8,9 +8,11 @@ import ( "bytes" "encoding/json" "errors" + "io" "io/ioutil" "mime/multipart" "net/http" + "os" "strconv" "strings" "testing" @@ -31,6 +33,18 @@ type FooBarStruct struct { Bar string `msgpack:"bar" json:"bar" form:"bar" xml:"bar" binding:"required"` } +type FooBarFileStruct struct { + FooBarStruct + File *multipart.FileHeader `form:"file" binding:"required"` +} + +type FooBarFileFailStruct struct { + FooBarStruct + File *multipart.FileHeader `invalid_name:"file" binding:"required"` + // for unexport test + data *multipart.FileHeader `form:"data" binding:"required"` +} + type FooDefaultBarStruct struct { FooStruct Bar string `msgpack:"bar" json:"bar" form:"bar,default=hello" xml:"bar" binding:"required"` @@ -187,8 +201,8 @@ func TestBindingDefault(t *testing.T) { assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) - assert.Equal(t, Form, Default("POST", MIMEMultipartPOSTForm)) - assert.Equal(t, Form, Default("PUT", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm)) assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) @@ -536,6 +550,54 @@ func createFormPostRequestForMapFail(t *testing.T) *http.Request { return req } +func createFormFilesMultipartRequest(t *testing.T) *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) + + f, err := os.Open("form.go") + assert.NoError(t, err) + defer f.Close() + fw, err1 := mw.CreateFormFile("file", "form.go") + assert.NoError(t, err1) + io.Copy(fw, f) + + req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + assert.NoError(t, err2) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + + return req +} + +func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { + boundary := "--testboundary" + body := new(bytes.Buffer) + mw := multipart.NewWriter(body) + defer mw.Close() + + assert.NoError(t, mw.SetBoundary(boundary)) + assert.NoError(t, mw.WriteField("foo", "bar")) + assert.NoError(t, mw.WriteField("bar", "foo")) + + f, err := os.Open("form.go") + assert.NoError(t, err) + defer f.Close() + fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go") + assert.NoError(t, err1) + io.Copy(fw, f) + + req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) + assert.NoError(t, err2) + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + + return req +} + func createFormMultipartRequest(t *testing.T) *http.Request { boundary := "--testboundary" body := new(bytes.Buffer) @@ -613,6 +675,34 @@ func TestBindingFormPostForMapFail(t *testing.T) { assert.Error(t, err) } +func TestBindingFormFilesMultipart(t *testing.T) { + req := createFormFilesMultipartRequest(t) + var obj FooBarFileStruct + FormMultipart.Bind(req, &obj) + + // file from os + f, _ := os.Open("form.go") + defer f.Close() + fileActual, _ := ioutil.ReadAll(f) + + // file from multipart + mf, _ := obj.File.Open() + defer mf.Close() + fileExpect, _ := ioutil.ReadAll(mf) + + assert.Equal(t, FormMultipart.Name(), "multipart/form-data") + assert.Equal(t, obj.Foo, "bar") + assert.Equal(t, obj.Bar, "foo") + assert.Equal(t, fileExpect, fileActual) +} + +func TestBindingFormFilesMultipartFail(t *testing.T) { + req := createFormFilesMultipartRequestFail(t) + var obj FooBarFileFailStruct + err := FormMultipart.Bind(req, &obj) + assert.Error(t, err) +} + func TestBindingFormMultipart(t *testing.T) { req := createFormMultipartRequest(t) var obj FooBarStruct diff --git a/binding/form.go b/binding/form.go index 8955c95b..f1f89195 100644 --- a/binding/form.go +++ b/binding/form.go @@ -56,5 +56,10 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { if err := mapForm(obj, req.MultipartForm.Value); err != nil { return err } + + if err := mapFiles(obj, req); err != nil { + return err + } + return validate(obj) } diff --git a/binding/form_mapping.go b/binding/form_mapping.go index ba9d2c4f..fc33b1df 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,6 +7,7 @@ package binding import ( "errors" "fmt" + "net/http" "reflect" "strconv" "strings" @@ -15,6 +16,34 @@ import ( "github.com/gin-gonic/gin/internal/json" ) +func mapFiles(ptr interface{}, req *http.Request) error { + typ := reflect.TypeOf(ptr).Elem() + val := reflect.ValueOf(ptr).Elem() + for i := 0; i < typ.NumField(); i++ { + typeField := typ.Field(i) + structField := val.Field(i) + + t := fmt.Sprintf("%s", typeField.Type) + if string(t) != "*multipart.FileHeader" { + continue + } + + inputFieldName := typeField.Tag.Get("form") + if inputFieldName == "" { + inputFieldName = typeField.Name + } + + _, fileHeader, err := req.FormFile(inputFieldName) + if err != nil { + return err + } + + structField.Set(reflect.ValueOf(fileHeader)) + + } + return nil +} + var errUnknownType = errors.New("Unknown type") func mapUri(ptr interface{}, m map[string][]string) error { From b40d4c175c079fa41cda2669c308485175cf2eee Mon Sep 17 00:00:00 2001 From: Sai Date: Mon, 18 Mar 2019 12:12:30 +0900 Subject: [PATCH 257/303] IsTerm flag should not be affected by DisableConsoleColor method. (#1802) * IsTerm flag should not be affected by DisableConsoleColor method. * change public property to private --- logger.go | 49 +++++++++++++++++++++++++++++------------------- logger_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/logger.go b/logger.go index 2ecaed7d..198a0192 100644 --- a/logger.go +++ b/logger.go @@ -14,17 +14,24 @@ import ( "github.com/mattn/go-isatty" ) +type consoleColorModeValue int + +const ( + autoColor consoleColorModeValue = iota + disableColor + forceColor +) + var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) - disableColor = false - forceColor = false + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) + yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) + cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) + reset = string([]byte{27, 91, 48, 109}) + consoleColorMode = autoColor ) // LoggerConfig defines the config for Logger middleware. @@ -62,8 +69,8 @@ type LogFormatterParams struct { Path string // ErrorMessage is set if error has occurred in processing the request. ErrorMessage string - // IsTerm shows whether does gin's output descriptor refers to a terminal. - IsTerm bool + // isTerm shows whether does gin's output descriptor refers to a terminal. + isTerm bool // BodySize is the size of the Response Body BodySize int // Keys are the keys set on the request's context. @@ -115,10 +122,15 @@ func (p *LogFormatterParams) ResetColor() string { return reset } +// IsOutputColor indicates whether can colors be outputted to the log. +func (p *LogFormatterParams) IsOutputColor() bool { + return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm) +} + // defaultLogFormatter is the default log format function Logger middleware uses. var defaultLogFormatter = func(param LogFormatterParams) string { var statusColor, methodColor, resetColor string - if param.IsTerm { + if param.IsOutputColor() { statusColor = param.StatusCodeColor() methodColor = param.MethodColor() resetColor = param.ResetColor() @@ -137,12 +149,12 @@ var defaultLogFormatter = func(param LogFormatterParams) string { // DisableConsoleColor disables color output in the console. func DisableConsoleColor() { - disableColor = true + consoleColorMode = disableColor } // ForceConsoleColor force color output in the console. func ForceConsoleColor() { - forceColor = true + consoleColorMode = forceColor } // ErrorLogger returns a handlerfunc for any error type. @@ -199,9 +211,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { isTerm := true - if w, ok := out.(*os.File); (!ok || - (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) || - disableColor) && !forceColor { + if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || + (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { isTerm = false } @@ -228,7 +239,7 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc { if _, ok := skip[path]; !ok { param := LogFormatterParams{ Request: c.Request, - IsTerm: isTerm, + isTerm: isTerm, Keys: c.Keys, } diff --git a/logger_test.go b/logger_test.go index 36231371..11a859e6 100644 --- a/logger_test.go +++ b/logger_test.go @@ -240,7 +240,7 @@ func TestDefaultLogFormatter(t *testing.T) { Method: "GET", Path: "/", ErrorMessage: "", - IsTerm: false, + isTerm: false, } termTrueParam := LogFormatterParams{ @@ -251,7 +251,7 @@ func TestDefaultLogFormatter(t *testing.T) { Method: "GET", Path: "/", ErrorMessage: "", - IsTerm: true, + isTerm: true, } assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) @@ -296,6 +296,39 @@ func TestResetColor(t *testing.T) { assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor()) } +func TestIsOutputColor(t *testing.T) { + // test with isTerm flag true. + p := LogFormatterParams{ + isTerm: true, + } + + consoleColorMode = autoColor + assert.Equal(t, true, p.IsOutputColor()) + + ForceConsoleColor() + assert.Equal(t, true, p.IsOutputColor()) + + DisableConsoleColor() + assert.Equal(t, false, p.IsOutputColor()) + + // test with isTerm flag false. + p = LogFormatterParams{ + isTerm: false, + } + + consoleColorMode = autoColor + assert.Equal(t, false, p.IsOutputColor()) + + ForceConsoleColor() + assert.Equal(t, true, p.IsOutputColor()) + + DisableConsoleColor() + assert.Equal(t, false, p.IsOutputColor()) + + // reset console color mode. + consoleColorMode = autoColor +} + func TestErrorLogger(t *testing.T) { router := New() router.Use(ErrorLogger()) @@ -358,14 +391,20 @@ func TestLoggerWithConfigSkippingPaths(t *testing.T) { func TestDisableConsoleColor(t *testing.T) { New() - assert.False(t, disableColor) + assert.Equal(t, autoColor, consoleColorMode) DisableConsoleColor() - assert.True(t, disableColor) + assert.Equal(t, disableColor, consoleColorMode) + + // reset console color mode. + consoleColorMode = autoColor } func TestForceConsoleColor(t *testing.T) { New() - assert.False(t, forceColor) + assert.Equal(t, autoColor, consoleColorMode) ForceConsoleColor() - assert.True(t, forceColor) + assert.Equal(t, forceColor, consoleColorMode) + + // reset console color mode. + consoleColorMode = autoColor } From 0c1f3c4e81f6e969a3e465d933faefb5578c54d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 20 Mar 2019 12:07:34 +0800 Subject: [PATCH 258/303] chore: fix invalid link (#1820) --- README.md | 4 +++- doc.go | 2 +- testdata/assets/console.png | Bin 59545 -> 0 bytes 3 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 testdata/assets/console.png diff --git a/README.md b/README.md index b46c5637..d3433ed2 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ For more feature details, please see the [Gin website introduction](https://gin- ### Getting Gin -The first need [Go](https://golang.org/) installed (version 1.6+ is required), then you can use the below Go command to install Gin. +The first need [Go](https://golang.org/) installed (**version 1.6+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin @@ -111,6 +111,8 @@ You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin ## Documentation +See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. + All documentation is available on the Gin website. - [English](https://gin-gonic.com/docs/) diff --git a/doc.go b/doc.go index 01ac4a90..1bd03864 100644 --- a/doc.go +++ b/doc.go @@ -1,6 +1,6 @@ /* Package gin implements a HTTP web framework called gin. -See https://gin-gonic.github.io/gin/ for more information about gin. +See https://gin-gonic.com/ for more information about gin. */ package gin // import "github.com/gin-gonic/gin" diff --git a/testdata/assets/console.png b/testdata/assets/console.png deleted file mode 100644 index 7a695718fa31f9b1b42cfa547c16da394378aeae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59545 zcmagFRahKdur*8q!6is=hu{+22Z!K;Ly!sXF2M(P4Q_+GyF+jbOmK(b?hb+ZdCxiD z_1_nL(a+Oed)Ho7d+k-#5kO^G3{+xNI5;>Axvx@caB%N=;Naf*AtSy$@iIOxhl4x# zE+-|f;jw(OG^N}ph9|8W!1N9H4_0%FLT|N`vXVY+ydbAVnIt=Lg3fOK|9_<{+yC=) zdO?Sq-nusZCtPnQ6Zru5Jxc0N;zSGzoFa`!%7(qn^D#4ArMf`P9;Nd<1*doK`(KFt z7VM5%wV&1+NfFPZdhg*Ve+tQt?Ykhv{nE7?iN>XbvmF3P{$(NLLK{#Z5P|piy41Zs zSj6UIMozb_49A6otj#`P^T*6Hng6_;R=PojQz%D=R7>w5XL=g%eJsaH;e*p@db$vh z8kiU{k4$$O;5Wab!Yz$RdQAmOz+D|dlYLHh;OQy&I>K|? zu({IX$luHC9EJeeKNGLPCXFcTv67eQ2^sk>=O;?Ir=9cKC~ z6LOkjrM`?0yx8>TpAk8fu20m+S*EMAp)t9SM&}B-6)g2`hkk4K)wlCrEzbbi+2%nR zyn%Pv*tSS`ra!-+a26n%LM)OzktKt_n7xq@b$eZxpHwL*FG)1%lmiOP3Ijg zdhL1#cYEI63xaM?mD|xA0Ah$emW^-4QH{|;^|EGDkNPdFmydIXH^M5j_1`q^(bmSx zML(A4zl-gTJi9F#N#DL1-qFjD)qiOb6DqD3Ysg9~3yR&iM9IHOccgPl|84L(#!>h^ zY=*tv5PdiDpQwH73vkFbP?Yr+)J`dRVPDS|8gu>}fK8gb`XFIT2um8+yTc}a`C;Kh%hmJ!AZNgX{)^zw z&O0*Uga3PT9$t$KnwX5pVY!#GjY(k^2zuGov@R{pY&}wu?RO zZ`b1AL_hnc&gaNc48O*{KqgUQG25nunyf_YrZzEQtHClkzrP5Z%A8wU{d>5)@4lVa zyFchuDYz$}=)!qrru*NI+4K4E$4vS5$oSTTzK}nQzA!Vf9aHfN?(F3xFFyF4x;~@C z0Me&^Vz-D>(Ejt<9*TjUtam+ska2W$gr2Un^t9S6RO^Wyb`d7s{~VaF((^uFZEN5Q zyZPsAllu?we=B!+F9yx2g(mpP9>*2N8H0-gUx=SuUY_rJe!UI-&FobWs=zco-v(~o zV_9SeWOh|fqTnZyd7H^lGT>~;anptnwTz%*HMoQjZ#Vz5w*`egvd|1*xim!vhoVp#Tr?Ta>>~>DD`@nnUxJiWW zv_-T8{Q9`h+UWFP4W7JTdtE=>YJri}_NbkQHHwD$JwKnq`0PO4KDtND+f4R`J$__L zN=ig0SflLq;3SZqr$F13K+|QwVq@y;zf$Cw^~30Y!m%5q`!|D81oxC`*PnHcV$@)C^bJ~W z%wXa<5wu5^4|D>xRFLg?Suq0_MVFVHAA>+Gd*(ea=bbOUM0yk_u%Ie%bMpSc(D-3o z2PQ6M^S$;7*3y5I>Q>vI-5Zv&y^)L2_$INxJSrah&6Nxn_kI zA209v-%+uh;t)8D#Xsogb(X~_zlM6gUba7Z1r!g-K=`xh#~QUj02d;nD|64?1}MY2 z|5GSpitMG#6yh<>xV!*oQv4(5q+426U9{KFIpBuBOr*kac|t)bXY~Ta$KAn0Us*b_ zIQEc;n+(OlG|^;2UjY_WRvHVG17cn>wWsvi2!<{#>Ll`zU66 zQ=O+_@AxBuctgd?Z?bhQSmrYqf_ZM7>-qYNc2C zE?ML4wAjUqGk83TfZzkiR;Fxy8D?1wqRIx3Jvm)*H_TzF@6-8H9&TP1op-7PiiS6A zMbi1xn!Q(rm}+dIHoN6cc6yu!6Y9GofO0#_&8eflpMiK{YN)yZ!eXeC|ITlsP8ZuWRiWF5@g`9})}qXn%@ zvSY0@cd1Xab8~B$&kb33OFft8P2fbwk1-gxrDbKC6qFKBnaLlx#FGWS2yUItgIGUh zn(RWUwaK6|&N8V89Nn&U5l`ARM>J#^Vfx(Oq*)9L%`Y#<8y3&wt~v8DOzKTN&|?tB z?LtqqN8?PBe_abJw-zhXhUZb?pkU=V-ayV7sQBg$s@S?t6NS+cUMrdN}9TvEWUvq0L~-U5QuW6R}{taT8@r$9p}5Lf6!WUIeB z%W<+3aA>KZfpea-r@j5#d?NJB=^1dMvb{`6N^r<}#ME41aqcGK3(INrdgU4Os-N75 zd(jQ$zpRv&|3{_h=|H%CB8psdOXA<<#8_%aiKMR=GcfYKlgsiW;*B@_3sQ{CS1Fd0 z_&T{=HS7+;?4YcnkNH;fRVqHKK1ToymqS$S$Mu< zQ8D5|30AeaWp(00eoxoB;1_jI*pq|FOU3b|-iGV*nePj;9fOlJWLEu0Ryu8Jyn~?E z4Fe7i&PNIg_P&Dy%lPwcXI3eH53;rI|GWO5>pat5O8AWXEr&IepbJ2zmR7VH$jwH< z)U&N#+@FzSx?9)KP2Sm>9G|OEW!p;6>%Fcc=QjKJ%cW9Q1k2vch<@wd^4_3?`YAVc~1UGtE^$O(#m9d6cv0%NfLs@d7~EN{;;6TWN;<(qj5eQF2l zZNOQizst)*0@~JW&0q*nNUg+dH8v(hWHfSxRhIlQIAVX&-Vk8 zBX067tZ<|%x9j~%zwzkgQn(OKsPB}Vu1exBe(1k*EcA^o-9gVC>!UR84B*!;B{q5 zyp~{Q?Eu=kt>coBk*@6SM?lI+gP7|I@Ele|E? zZZ~SI_CL+8f#nOXm+pFwv5DVMbiuI6AyG1!Ed&dlv6#rXZJ#;p6vy?CS(fm}w>?So zef}s`eFO%^8+-bfe;TgX7#o;4xDTZyFIX}Gs`Ltj76eyjK-2JhdtDh zU392ZY+((}J#-!7qNe@_yCi}UvsM-r!IY9@#vEzGfz1~WPfsM$FV#}c{q|Y*$l_`* zn4#hH3+H`)W5)_+eORaag5`eY)%6&Um3ubVv(C;_POF)dSv6xCIZbYjdCEjJPoAdQ zo~N0D)$M|1rIghq#06O5a;erIEr5H~7o-Rq3^B917T%>Au3Nm5?wz{DDuH-enn5j$ff?PmxeRi3)P zTLckVq=O>3RC7@2eGW9}BBLTpt5C(;t@U>$hPN;2RV^Dw1elOhlAjW?lET$TrSPK0 zE$!#>`T2+fg@dIEYFi1-D}q*R*IwBuPF1YtxX(VN2zQRs*FEDp-i~i**c9K>Ay5_wj{cOuo06wZo{avZ$_R$5T?OggiM#!q%|o zovySNwV7r-uN|Fni^c0lpW4swHQizg6+V8nR>Yc}d3%Kfa&;to!0SO#~W@8v;Yp!$q?)18ZjBQD*)(4Ysl@ zU~?7<+hw^9|Bji5cP#HlPcKf{2eQtX>(7BglgY_UGkB{*(H%Uinu_$|^6nHs%@)zqYBK{u+?Wa| z)xPs$%2n?|80Gk#zu;V=T=HWRMarapTBzpTH10wh&hK+#peB`wH5X=PvXW%tGugc0 z`(plNix7(@c7J9dKJ!Vu3cO?Su-#GaT0 z++vyy8Rbd)f#pYB$Bo3UFQu^Y2dn!Zj=UY=q(4N^4@lJbV&@)BNy8--<1_4CoP;7*pX;`S4*8fy53 zS@D*R4>hX%*k5CugJ!ar#1*18U|M?cx@nlmuv&U$gtNIMT_~&^9-xDdZd3Gdi(XQM z97T60zA$wSG_C#^EOnra&`{luvtoh%3n{Z-Raa?X>~Ko`Xso_LIW+1q!SF;((g0)s zaMIfjES-)@48JeB^;FY2a}1hDY;T#DT_Qm7?dq(d{|qXt5ld-VvR!Pn!p+amM<_!< zLz6T%&Zp$w+}xZdX#5zlCCIl(Q4ex`9vz@6MLTSFz2aO zXOf!*edUz=j=0>WUZSkxyc|u$wO+q+%D0MJ5)xCncEMuKb=R|mRZRdHTnO6ebE9TQ zA)^q#>{$=`3GPbW!rFei=xqfwit3Bx?AGVUruo>@|N5@`&z3XO(b7+J_QVSX=H!e} zos%QRryS6#%8$#3&LuEuMO~9IMryc7VIHMB_C36Hc1F=o$$-Fr*J9XM_7MGq11j5U zf`TciP5JYqIVkYsjE^n*Bl5V~kfUyOzq3bQ1xcqC$ZnCz@}%>q*uIa6f8(syrL@>4 zuemobsZ6j~%dG4LIPNlHXSrLkJ15Kr)!jy_TU#cUNn)SZrO%{G9q-8?SlsSoTjVQd9PW%dI_LxM@^zoo3LfmY-FD=MNP7u)-#`c;IYbFbA*(1f1XPx3L( zv~!2)Aa|s4>vBgrb5Y3g6AmDYgA9J-B+jqqUS|00<)bfCP<8+zL1xiM@ndb1r|0d# z2R1Ch@S{+=qaGcTz%PPIZG>w@@WpIcp;J2HpK(D$QMjqOzjf(H($Xvtg!0P!0>~N= zH~U$o{3^;E+&kv+XSJ!t_TERIuf5ATiIL?QExAlKsj8Mkaq~I*_tXOGuLf%tFN2%T ztwFO+4!)#W5HD+RBd3{nJ-aZUr@hNa2`3d5)nbDM*7VHGAlks2ptD$K61F=04f(q$ zAtB)d{(pSqncmr6O6|oJsO`cGkY~Q)N#r|y*^#<$Rq>pk$4aXX^<<3Nf*UJ&J(*n z(=^o<=mLlQo?C>xm6<{N>x4lp1qS-s(kE$Qpf3FIdgse(O!>*re-FbTVmF8Hx#K7E zGDlUxcnB5Q$nWf|Ka`kEb^M^9-N$BRZ%H()@0tDKf_-Ru_a|QvFzn?FZe^aAWl$P znq{Q{CpPTPhw`M_x^VCmSIX${iR`NqRJ_d*Pnkbr7UAY3;=*JfsIJFqH<4Ex)o(p? z8RT7SG=sm~+0tOB2EyLxHYhYA30Z0RV6;|DS!n*LSNF_cb8#T@s2nvb92Ngm1w@YE zBF$P(DpLso5N1Ci<>y8(*STbV{who!wYX?mnITG6)5%M!N)L@lB(MSzVAF?jAh`^8 z#gycn1xBDP5~M2_Q!mUkOXo-3&y99`vKrEB2}+pY#vz0MkGko^e?XTB*A6~8o{b}TsHXkb=+F!OtBKlum2R7j@B15i{05h)mkVy$X z7MgD%%%UHJIlM2~szKY|TFy$Z?fjE?L0vtO9pXU1{)=x$R0@Nvj3VO;xtR=&ii|2~ zj5A%46g3d1D`96!^A80^K&zF3zk?0$RuB6b{zJ1njr}tI-?)$&pn_Sfeb!e_DFZ}Y z9L7}29pl4ED;=;OC#GO3O}9DObmo2TgLR|WQ;|q`h2gO0Tezg0ASYe5R?n)8CB;M4 z959Ib!^}*oLS?kbd!nxKt5ibgw!25;t}+{Bysz(Oolg zT7;#>;so*rF9|ymi+I^6T{W6oEIFkb7lN*t`{Foit=X=vFs!fQ!Q;i81tRjH20g?7 z#@IQ37hkpI{KD)@Mpd8gUQVodi)_axLjo~iR6wlId?cDOjRkYjN|HrUAXfYNPTW|F zaqLuB#`(8Z$8G5m>FI#s9)9m$IqY^%1t0LV$W3)B|$|?NTh3+0LeS{ zp5Q+PjEz=k&gVob>!lI1SrJ0fe`+14In<8E9FG~E#QDP|lX}>>aR{6hzPEUuY|&09 zd$FK5+WpmNMoCsiVye*R2Md$-c+?`rsH2S8X#qrVO4PTG6H&S5q2z(p{d@VrLvu`~ z>0jyVDX{x*uZJW2SXK$&yLf)yyWkmn+RC)u;WW`j3m{8=@TQRo zjF`rCKb1JE=yLLX#PMlxBI}Mp?guG<-eMo6zdX%D_GvJqsjm4Jt1nn4fL%|5522(D z;`@eD@Vq2}b|f{+wsQ`~Tu^dxEU^Cc`m|)^^9-9t-e>UpT4=xr74Hq$Z)0)DC{#fy zBxS5xDh z2oh!!wpSfcbY+H`px+E;ITcq6ZPV#@$Gw&t`)O1rh7J1?O8*I6RkUj|z|a z^X6a6*_QoAxHE(tH3boj+7<_LRCxRKv%&(iuZ`9^@C1;4S{(jfKt+&FO9C@{$X=5>n1ZZiQqA+kv1O|fL%>r(UdoO0oLYSB*{WBmocoG@nn zX8Uvs5ojh^GbE2;DZogU3a4&{_A~brxEP3MT>Uh;Fm54m+|ghwXSPLN!I;-A9WNqUczJ2M$C0+b|zNF3WoO|o8ray~pEJZ~`k?zil`S2Z_ir|hG7ACDpaK^Fi1wn^K?Fa0GU1j z36-5sc^IPJDOC+|cM}{IfU|MQefqKcW z*C}p&thG2%`s@Lab3O9X!}Xzht1|5{zy>jYBWKL44RgWF)431rJ@t-S=b!j6lU`h4 z0w)5v927(XqWfC|OJ;Z6jzW;vL$f&gIDi-@EAGcOYE1N5jZSi)2=bQh%|^I=yw2cv zc+X@{wRKycyfeinw9`kdR>M?g)`uh40`hGcM+>}g)bPj6M4Aq(O5_+0m-)wM$L5Q# z(?*2T*{KL}eY9>h+&-Ggi=0;6Hno?&Oz#n>mNtnFYw9k$B2pY`X)QXnwP7p8G_^|X z^l2o8t*)V|wmUFRM=*RTYm+MBAz&=zJULi(7B*4W(5GIX(8ar;Hh()S;3Q)3s-sA> zOb#hX66Ds-qE}1=&Zv4{szC!rIE3PHy0GFz7C+?;|4hgrjnlkAWaOP#y$P2i6XOIVk8QzU{`3t+vU(>ZBnMrW zjq>C8qD!0R1Q!^EhezUq_y@yIe?bMi{1elQYKa=YTouuOad=@f-P_3nPNK!Vlo{k< zp(Dae&S~zA0t+|lUn6FheuwA?m{G1nI^p)~F&IXa`?K!-qEMn*@@9|3QRd}N3OT+MsZyonU0WCIS*_hc(~;L(F~s?rb5;sbk#-P zb-rjn{rLa{JJo@GTPm+Udfl)F7oyvN8aWF|PaB%qpddex`9`5B${@1%l5Fo~{QLm&BUI*zoiRo(we+5dS7UMcS8By#_K(=XSsAf0c`otEnT*IW z?R5lrc$R#g>Fn2yht>jB4S(yeblnII$#Mh%(q|va$L8*I#}YX;mris{^_dlv5)UgI zGT2XR&Koh}Z<({)saY{BZA%At$5Vk?X;E3(E=H>jgooHGstdqH@ z%%OFyaK`{IEM`EARKgT`LmhIMd)2^V+&opy*MH@GON)-t-s~RZquTOBN% zV?>j}s3a`8G6g*GhJ0#C5tpS3B(yfy4v*tO`b=5Ilj%CqA0HWw>=Vc)L?E(yOdz;$3{i`6B3%}DsF z{D6s`cfTg1lo2~{?aAF@g-P7G0B)lnuZxp?v! zf#|X5W9(3TR9s1%B+Q70!*5jwo!a+3LFM41zO&THNI0Px?eO=Z&g2ocI@J2K)Kq%q z0C|@PN?&dQIf5%`U6Y7+2|3k$fBmP@9-WoO!9uys8u5+R&dbD6g;o zr-SCe{_R6WZvIHJ1OKJhnGDB{jE=U+4ees&hrl5we!y&KXh=|@f~j#&OO4P8f^CtjL#t&Y&j>^3_I?o2F+-=98S(a4cg@}{wucK zW>lbc{RI#5e#6Q6;43{@SiWz^r}3 zb81U`9O?Pp)FeA*(=+mRj?;_FD^}O*Zi5z#_a5xV63n*R-QMD1o7Vy3N*lPa!U&R^ zc&o=evLh)NFQ>db4h!Gk?j1}O$ku{PVIzKORJ`l!b(ek^MSd8&&sG!J^gO2zM~BVX z(+uZR!c)X~R5u5Ub=y%l|42Dz5^g+K-h>sB?hwAZ-@g?vxK!}{_B`;5E)k8lpm~p6 z{9%&6j2a>u^$p=PQpO!Uv4ZteX!`Vj07S3N4b7=Nw{U+sBMQ;2!la-jA3N81uW+2F(Rn=38 zZPF?Z{%s<#+-+l5a8!9jH#6Hx%xS7uwFF9LUfENjeH>3M#C3W4w&I3#T~mWJHQ*G&2Y zO!SWhIkBVW=3mfa&OV@p@e~@5`AtZ+DU;(qV!k{{A#X(T-M(+!9l@@%bGm=sb)GAe z>?5(ahq6$bn3t(d{AV+-Ocm1dyA+fCiPtOLrewiaPL;l%SBH(K1tO%i7m!k_70wv{ z;WP&%FM-Vuf8$vm~o3G3WulCb{CSP#gX_G z*M*6sb~H;jn~B$dm1FxBDV|#cC~tMezaILen}o3xak4(fII*8;so6FWw&Eycef~KO zsFJ4&_&zn-!sffp*Ewa#4_0#A{>*Q;Hj8pr$BtKB`RP2{>=!br%qbmx-i`!NPtZSG z`0+iv=&?vv1T{ymU=kV5gHzgHyH++#m^T)BeC`UZRuVkbEMQMPO2?o9Ir7mvH&`mv z+B0Q1uli-~rm&FK4;*@rk8(-+R_ugH2hPr|yh1IF$d<5T2|0|ppQZ2OPUvsLW4thS zPaB6;6#BdWtz~p%&;C>F3#*`q5~z%g_X_?Gp%$*9;)Zr$%pC_Hk$a?1d%tysMGO2m zTLrBqR))~ii(IR6hS^$Tryk-t6Au${s-z^Mi4~7pgjUiMq_Xr6$i%pK>T1o;9p&0> z&)>mBy+QBkv^A?%H1~l9bJsc|P<2ghH#ZECABZ=G@W>fxSjdT-5!%w1fnFOOPmg#v z5i08))LyDP)ap1P`ge(Tb}RQ%Y_F);->hna>Y7^Y&ngq3;3OWM z>um^h-7nc-9cOjDezQ64qTqThafcHvHQKxw0iGfo2-dcAYDMRC7BaKzG1)s)p!k@qwQoTu491&ABYDN-UPcc%b{G^Z9 z+tP_H@~;$cotq2yIX__>)UdEP%*Vv2-z>2a9-})wrqOGwhb1|F_(#&Sai#PazK8q% zF|yG$36-HZfuU{5K;|FfWm2+5MRd9giLG0szvXEVk#=t^zPUZkEm0U%AFzr-C51xU>7KLW0215AI3<$pvcGUAi99+nZ^L{zbKXxUaWc0Q;!!pGvg3$K z?s^?!Img-qcWSX2Bjj?V;CU=hgA$0>k-+!%%x$F~uHf^o`z;ZiA`{}ZYgi`Er5Fxu zI5nmQ?eMKPH18Q6vQ%$|24gAS7D=Xh^-wXBGSrBz_IVqh_P(^$1LD*Ape+y(4Pnx3dS}g z-)Me_%cZ!3({g3byl})Z-Vu+U^-k*$Ps#TWPhI%SXwCt``s+}yIz-5!o~aumeO}5SB6|AC~2UazW0`PBXx*{KkbK5fWH@pI2Ecv)O}>W zJ{&YOAYW`bfmWegTA^XnH&QJrAFqf(T9#Ehpsy4QgRBkzw$N!~yd&>-Q0VUS`~_Pn zt+se}yp;Y#!i$2}jED;`kyZDW7UkpKT@gp!^u=3%Ck04|25NuYD%m7PBu1FV%*3M% zQ))&0RqpNld;80A&}GfVR|NX%$9tkVwxXB9{{Pi((I zEcdI{_LBxLl6B$p9BFbcU4%!5aAb)bomiTdFp5@&{iJ-uvREF)hi~u_oqw#G^1mQ$ zmUlU*`Lk##IWc79(`*D>DU2ar%TM=t)R>Atp*}K1vZw@9WQBE}An9N}=j2(YaMuFg z_3DUaF!o6K;$80u#~pYSGe|SaPhHA|zM>TXq`E8*hMTfT$_Z{6bZs7zfpUoG>iSBC zI8HoZlU$kZs9Hi5ns&$loDpA@$4?zA;CK4>FCvEG;RF8#T;=kXWTghjhG|esTSuiW&C2AdOr)d^B%zkQvzb~Jn^dK~M7YreLa6Y33f?cdH_y9v1`>A~ z{yvtSO*NkN#rp& z=1z(k1NG$Ge}3~*um@wDMinZO8eH(AyrvSX6k2Fr8kaw(06~P4*`s2_az7bX*xoYC zVa;QO)ebji-&9uBYB_RtfL#NE$;6UO^zFJ}V*6El^)jWyW>{FIWq{b8foSTJvsddT zB(&>wVO1SMPds$}tg%L>o||_4d+MFip6B3Hu~50pc()#4XJv`_9x%tR`f%Tjd@)1Z zG8?Clk&APt~%Vg zuS#gmE&u5zKFYiKY!gHrg+AK}rQ8gZbZE6W^V+&E;)p9UDsD==M3v}_4wi=tX%n6v z{U&ZI?Qb&RUYPe8VH`QqKzxw@Z(EsNJ#3-32!iz9MJW{z-PkizoyWyNOaxf1&%W@K z;ZP4|syzU&k(`6hzMVyV*dqb=C0+R6yMcUeoh?UUKfUWM%3bobuP66DnRMh<>C#1Z z>6@LhKWVLfhZ*myZ75pSF>mD@65T?zAn>`xbSlTX2l_Ymj1>a z8}yBvKW!p1S!`TuaF{b5{s}WXknA+cdnUVgMI3JkHh_AYyv*c%*to9Nxu|NkVzDsu z*#8698im3X|J^*xz= zyyJS_mKALUgnt3|RKdi$=#dcl9B=zcSGg1$7`fAoECgkaYW6AdYdmu)3L=tj@8~;o zw)|jIPtD}wfc%S2vgM=BaRbk}5@ht1UMS_6+&ZV`*%i>>T>63TFU<-vi~Y!SyM-V@ zXAcumkj2KGO>g?InOhFmt{uNBuUc@KOWQ47as1m)GsP^+J*#aeT^+{Jx&cSxZBaf7 zZb0>m4I9GJ%U9W5L)Ru9G&i)^A}+pZGOP)2KiwfQrM;4|d=cEIo$X>deBVf$ z7+SnD)Nr1Asi@@oKNv=fbj;mr==8&@;`!Ij(}^Zuk9q`j;_;NF)LFa=c%S)+ZNGUu zF!-}V8*_+9hD?J8G1o%k_aXt5;5#Sox&TU8f>?~PI<7?E>e}Q04km z+bS#^oOk+`;OkmBZDam^2k<8+S!W3T-Pnn{7s1g#@LuEt&05br>_^}| z`zCvqfN3 zH7F^z7Vz5X*Z8B|BoEweQsy1^qy6lN8WCY;Wj1_d246nzpm$Q{pCW|(V`U1V_@p#5 zsY=dgV*rE^nE1vg4j$lnO0iVgiJvZM*T0z@1#-q!SmAA1GksR*FzfC6+Dt31v>b+F zO`2aMQ!lDif5}aSyD$@BTJ%nkDA=9pZ8y!?7srS?ACX(g0RO3n-WLq%T(+-Tp7zYf zgzz>g-P>VI{&*P^T65K0V$hpNqN+5n&pi$dG&>Cx9vgN5gNaSs_y(PNTf_|=40N$l&=J6Ny6guS&U@Xkp{WlotCbr$b^ z>0#ytlpwp18oB?Il3?lKWE`cVRhJ~vr=#|tBi^~ic;XmH9&89e+@MZ0MR5!qEbF_* zGujH&#*JDpGlOr~r=SBJ=a)tg!j%NKNSeS?t?Zrl!0&4r=v7xwN1MMOzkZjug7ZpO z%5_mu7%qh|{8OrdC-H{lW@?px$I6mp_PuW3kQI@ly}v=}4E)&h3x&s6^C6i(y3f_2 zqIpW>{Pp|5nAs72VC48%*!ZLi<`ZADird<5=9zb^Z|@-&ix5#{+jAL@-Z28@RF&tx zejM+~^@(Nrjjcl(gN{i|VHM-N*Pc+Spx*{w%lpz&@JQDWj8+BW8lKf}$pbS3YA!tf z8H75Y0p$xd_$0TxW}!m#Li<7NyaSa>L`EAq%OzT@!70q5c?XxgTyWNBdtwu&-|L># z!{B&W5v+YKyEmJcPUxhWd)jSF|4N}Nx(7@4=e2bwGz;u|$x5A$g(vw<5KTCm5ZNp~ zCjS)QAM^scP54&VX-4(#PRAXKq0eCceYE@gnBe-`#GmfD{%7gA*^kP-j6fq^a^ync zFAygq;g(*HYJ*XaVq@$7s4v-%T)M!g_=PyrDr^Jb7E`I|wl(rBWki^sycouxDYLRV z?5Zzc(^z;uo6FLC-Lf*Rrmvs&Aw5X+_&{a&3ITqi)94S$vu<`y4!~@}tJZx&beu#uOHi@$P)0TL)GnsqnHQn4&F$tz-S@Jc`U3q4oQHh(hIWgWYFIZ=X4} zVds*0z|5unGah0Ku@j{?6L^XrRm`N0o&4JY2$}T}Qo1(mAsA3%jq{*stYh7+E40Y9 zmC3A+_I=QCE_ZsUbEvAy` z(s0gG4Je6U;`kWjM$>iu*E$28?e6RvI;Ri$G&~tiSbeso3 zKk_YOoyQ6o6L}82_I0%Lt~HN-7mc4O00sX>fOv^!Ei7PF>7MD@dzV^MM0s1QQ-|?E zhF@q?|0fk7@T6WpB~|)e!%^}kX@-d?|4a&JvGh#>0rFw`xIMGAVr;fm{^v! zOKxslY=sN(emyq!57?iP@$<<;Syy$}sALg2|!7AA#|}0#*W= zB$Aq1HD8qh>krrEx2>cWW(46wAc!|uZ!q?-XSMj%} zG4VLpQ@Cek*W-`}B5w4)Md5b(vL-)~+E;)qwyye>f@<{H#yJLR@36jH`GRcePAC z3dkt%6EOYfC!nlosrXNSVpJS94iubf1xKcqw*y$U7xlsnJ<#&MD5UzpV;W#2W#w7~ zieQ0>rK*v`b9zC_fdUCwIJ!36D@UYNhp(Ys5Q_|xiCQB=^8{(_R#U1`S#823_8m{+ z)2{@}z2|RbFD*JMta1LSZ1Wup+<1Q*%Rs5I1nebt1phn9kyD(V|iZ}-AR5OVM!sdWMIkTi% zpLKRQ^LKhZHCr7NN-T(^0$c(9StnHoq*{v*R&v~#Oll%gr^D?h1a1^ARi4_VQRZL7 z%q~$Y&B>7(%T7tFXz~)8BVHv--!2jR*0!!3GS|-x*lxp-)|UFL5ew?Wbi;qA8m;tg z>#4M+nGT|}Q6}OH;o0;3xFW|X(d~K@WGbw!99hYl#-0#V$hM?&eU20R$#9OzMwp z!8N!uxLa_S!EKPi-Ok+i^Stl(opb*5nl)>;RM&M?S9SNU{i0jJXInXu(HN#ufxo6! z!UdTudE@jPjj9$MBu(ld?5=O(_$@jePc8qaZ@(vHcoa48tTxx5GYd^z8GW% zp6Lv03u_nm}Fh02cmuSADHp2R-_RT=eCe3z8J`zu+i$qA6N2kU7Y zrn`mYl^z8`H^+oN(+N{{NDZ=Qd&w@8Zi-pyW*y)S#A5G|9)Ay}zxui=f0U(Z`(3}- z$*gGo2FYyO8aVZL=OYStT=m%A*f7p`?{I9Ur*o4-OhzYceb`74x%jIHu%=mwd9fGw zRivD%<}6)Nk_)qdFX;q%KmIgjhm&%qS}V36eU__fuv%>i@^JbNU)|DZOnXVj0cH1n zHTlAaBZM`1_l{7O@Wq>t$pEIg@6ZQxHdlh8B#{PA|$c2y(G~CzP)ycGp5PEQLHndtp-zb;J1ag;B?CjZs(*{HfEcqUY{qp?=0Ibv`<{ zT>$B-h)CTChvlfCiDc*dP>x&Kc6EO_T`Bz8eG<|xwM5RRIhv_9sP!|>bkc%n&SDJV z!0@WprPwCjB*#Sv^|)2hwkVC=k#xQC8F5WC^UR25dK-}IIPw|C%Sb%jT<6w?F0T9y z^hZ{`pseFdzu+W3GN=+vC(B*6?oP0x=V{vWW1ywo{6{tKv3I^rR$u{j=Vp6IW$2pd zrT~NG817fgg3`qURR~zU=wJ?u&@|B{1yrGs)JwBUTN1I4l6KeX36x04WOlWDG6M`^ z485e$uRecs{2P2RH0qq!^|F`QZExdQNW1IVj6VQ?dFJ})fw|XVRd!Pl zxL$e>B^|zo$+qw*PK)n}lpC9F&|H_s`jcFlz2>4H6d9Gls7a{sqEZz7+U=ltfyuzx zN{7gW#H5wDy`V^NtmF5y%eFy&zDrEf4ANH18tj?AE;$SC!9NUd80XaPx#lA-zLma` zT9;e9WEwnK+Q~G3cJE^-1dOpnUb4Hg-(Yx{9^YE-pS+_Itj$DYoyV_nnB|#`Xy};a zMwg99-jT#1vf=pi9NXBwO?=ZagLBiqpQ;@>l9W|Oy0?sKO2+D$Wd+&~>mimz0HuBx zHJGz8|IqVu%rZx=ojx*hPrDSB%V5%q^4Rj`3xy6$=h}Mfyc%Y;bqe2wA%i&wx`!P& zu8WaRB$}@~E3Dk)T7GUC5dOWT{xZG@Ommve*ml$WeAWrH+;sWCC}N~M7{`68s|8wG zBY1jbYS}e#unTh_QnXO9j~A(Vb819R@ZdL7m*;GV&%$T>!phJt?2wK8S_jlwi-ljf zApaGkOLQTzUz$?1^RE38RGvu`kH_A)wnt%Xk9XxoruW?6K5W~19PdbT zu)ERM*;BvDja1#u?zfzt`o3iTM=IYh1rQafaSTE6#e{y<5~EO9dv>)u=sbCQPS+=J z?Ng|1m7TUj|N*1&Y$d$FZ^BiF4fC%bwqhpU5Y2V49IIE;J>HD5efMH8Wixwmu8 zjdz&t7dmKOxthD4cAj)u_uTyD#B`mpk?+Lae>H+ZT|-7JlEy+2H~skYBu-o6@+q
?hY(7P?kZ1wD_zt6I%hfC|R7KdZM)`&t88E6Ua# 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 From 1d462bbe3713bc2fea40ed45c80a06ce856d379f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Thu, 21 Mar 2019 15:12:06 +0800 Subject: [PATCH 259/303] chore: update ginS (#1822) --- ginS/gins.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/ginS/gins.go b/ginS/gins.go index 0f08645a..3ce4a6f6 100644 --- a/ginS/gins.go +++ b/ginS/gins.go @@ -125,23 +125,35 @@ func Use(middlewares ...gin.HandlerFunc) gin.IRoutes { return engine().Use(middlewares...) } -// Run : The router is attached to a http.Server and starts listening and serving HTTP requests. +// Routes returns a slice of registered routes. +func Routes() gin.RoutesInfo { + return engine().Routes() +} + +// Run attaches to a http.Server and starts listening and serving HTTP requests. // It is a shortcut for http.ListenAndServe(addr, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func Run(addr ...string) (err error) { return engine().Run(addr...) } -// RunTLS : The router is attached to a http.Server and starts listening and serving HTTPS requests. +// RunTLS attaches to a http.Server and starts listening and serving HTTPS requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens. func RunTLS(addr, certFile, keyFile string) (err error) { return engine().RunTLS(addr, certFile, keyFile) } -// RunUnix : The router is attached to a http.Server and starts listening and serving HTTP requests +// RunUnix attaches to a http.Server and starts listening and serving HTTP requests // through the specified unix socket (ie. a file) // Note: this method will block the calling goroutine indefinitely unless an error happens. func RunUnix(file string) (err error) { return engine().RunUnix(file) } + +// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests +// through the specified file descriptor. +// Note: thie method will block the calling goroutine indefinitely unless on error happens. +func RunFd(fd int) (err error) { + return engine().RunFd(fd) +} From ce20f107f5dc498ec7489d7739541a25dcd48463 Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Wed, 27 Mar 2019 23:14:00 -0700 Subject: [PATCH 260/303] Truncate Latency precision in long running request (#1830) fixes #1823 --- logger.go | 4 ++++ logger_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/logger.go b/logger.go index 198a0192..5ab4639e 100644 --- a/logger.go +++ b/logger.go @@ -136,6 +136,10 @@ var defaultLogFormatter = func(param LogFormatterParams) string { resetColor = param.ResetColor() } + if param.Latency > time.Minute { + // Truncate in a golang < 1.8 safe way + param.Latency = param.Latency - param.Latency%time.Second + } return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", param.TimeStamp.Format("2006/01/02 - 15:04:05"), statusColor, param.StatusCode, resetColor, diff --git a/logger_test.go b/logger_test.go index 11a859e6..56bb3a00 100644 --- a/logger_test.go +++ b/logger_test.go @@ -253,10 +253,34 @@ func TestDefaultLogFormatter(t *testing.T) { ErrorMessage: "", isTerm: true, } + termTrueLongDurationParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Millisecond * 9876543210, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + isTerm: true, + } + + termFalseLongDurationParam := LogFormatterParams{ + TimeStamp: timeStamp, + StatusCode: 200, + Latency: time.Millisecond * 9876543210, + ClientIP: "20.20.20.20", + Method: "GET", + Path: "/", + ErrorMessage: "", + isTerm: false, + } assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 5s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m3s | 20.20.20.20 | GET /\n", defaultLogFormatter(termFalseLongDurationParam)) assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 5s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueParam)) + assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m| 2743h29m3s | 20.20.20.20 |\x1b[97;44m GET \x1b[0m /\n", defaultLogFormatter(termTrueLongDurationParam)) + } func TestColorForMethod(t *testing.T) { From 2e915f4e5083995154f65a600c86582b5396d02a Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Tue, 2 Apr 2019 04:01:34 +0300 Subject: [PATCH 261/303] refactor(form_mapping.go): mapping multipart request (#1829) * refactor(form_mapping.go): mapping multipart request * add checkers for a types to match with the setter interface * form_mapping.go: rename method name on setter interface, add comments * fix style of comments --- binding/form.go | 36 +++++++++++++++--- binding/form_mapping.go | 84 ++++++++++++++++++++--------------------- 2 files changed, 71 insertions(+), 49 deletions(-) diff --git a/binding/form.go b/binding/form.go index f1f89195..0b28aa8a 100644 --- a/binding/form.go +++ b/binding/form.go @@ -4,7 +4,11 @@ package binding -import "net/http" +import ( + "mime/multipart" + "net/http" + "reflect" +) const defaultMemory = 32 * 1024 * 1024 @@ -53,13 +57,33 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { if err := req.ParseMultipartForm(defaultMemory); err != nil { return err } - if err := mapForm(obj, req.MultipartForm.Value); err != nil { - return err - } - - if err := mapFiles(obj, req); err != nil { + if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil { return err } return validate(obj) } + +type multipartRequest http.Request + +var _ setter = (*multipartRequest)(nil) + +var ( + multipartFileHeaderStructType = reflect.TypeOf(multipart.FileHeader{}) +) + +// TrySet tries to set a value by the multipart request with the binding a form file +func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { + if value.Type() == multipartFileHeaderStructType { + _, file, err := (*http.Request)(r).FormFile(key) + if err != nil { + return false, err + } + if file != nil { + value.Set(reflect.ValueOf(*file)) + return true, nil + } + } + + return setByForm(value, field, r.MultipartForm.Value, key, opt) +} diff --git a/binding/form_mapping.go b/binding/form_mapping.go index fc33b1df..aaacf6c5 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -7,7 +7,6 @@ package binding import ( "errors" "fmt" - "net/http" "reflect" "strconv" "strings" @@ -16,34 +15,6 @@ import ( "github.com/gin-gonic/gin/internal/json" ) -func mapFiles(ptr interface{}, req *http.Request) error { - typ := reflect.TypeOf(ptr).Elem() - val := reflect.ValueOf(ptr).Elem() - for i := 0; i < typ.NumField(); i++ { - typeField := typ.Field(i) - structField := val.Field(i) - - t := fmt.Sprintf("%s", typeField.Type) - if string(t) != "*multipart.FileHeader" { - continue - } - - inputFieldName := typeField.Tag.Get("form") - if inputFieldName == "" { - inputFieldName = typeField.Name - } - - _, fileHeader, err := req.FormFile(inputFieldName) - if err != nil { - return err - } - - structField.Set(reflect.ValueOf(fileHeader)) - - } - return nil -} - var errUnknownType = errors.New("Unknown type") func mapUri(ptr interface{}, m map[string][]string) error { @@ -57,11 +28,29 @@ func mapForm(ptr interface{}, form map[string][]string) error { var emptyField = reflect.StructField{} func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { - _, err := mapping(reflect.ValueOf(ptr), emptyField, form, tag) + return mappingByPtr(ptr, formSource(form), tag) +} + +// setter tries to set value on a walking by fields of a struct +type setter interface { + TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) +} + +type formSource map[string][]string + +var _ setter = formSource(nil) + +// TrySet tries to set a value by request's form source (like map[string][]string) +func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { + return setByForm(value, field, form, tagValue, opt) +} + +func mappingByPtr(ptr interface{}, setter setter, tag string) error { + _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) return err } -func mapping(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { +func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { var vKind = value.Kind() if vKind == reflect.Ptr { @@ -71,7 +60,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s isNew = true vPtr = reflect.New(value.Type().Elem()) } - isSetted, err := mapping(vPtr.Elem(), field, form, tag) + isSetted, err := mapping(vPtr.Elem(), field, setter, tag) if err != nil { return false, err } @@ -81,7 +70,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s return isSetted, nil } - ok, err := tryToSetValue(value, field, form, tag) + ok, err := tryToSetValue(value, field, setter, tag) if err != nil { return false, err } @@ -97,7 +86,7 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s if !value.Field(i).CanSet() { continue } - ok, err := mapping(value.Field(i), tValue.Field(i), form, tag) + ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) if err != nil { return false, err } @@ -108,9 +97,14 @@ func mapping(value reflect.Value, field reflect.StructField, form map[string][]s return false, nil } -func tryToSetValue(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) { - var tagValue, defaultValue string - var isDefaultExists bool +type setOptions struct { + isDefaultExists bool + defaultValue string +} + +func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { + var tagValue string + var setOpt setOptions tagValue = field.Tag.Get(tag) tagValue, opts := head(tagValue, ",") @@ -132,25 +126,29 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri k, v := head(opt, "=") switch k { case "default": - isDefaultExists = true - defaultValue = v + setOpt.isDefaultExists = true + setOpt.defaultValue = v } } + return setter.TrySet(value, field, tagValue, setOpt) +} + +func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) { vs, ok := form[tagValue] - if !ok && !isDefaultExists { + if !ok && !opt.isDefaultExists { return false, nil } switch value.Kind() { case reflect.Slice: if !ok { - vs = []string{defaultValue} + vs = []string{opt.defaultValue} } return true, setSlice(vs, value, field) case reflect.Array: if !ok { - vs = []string{defaultValue} + vs = []string{opt.defaultValue} } if len(vs) != value.Len() { return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) @@ -159,7 +157,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, form map[stri default: var val string if !ok { - val = defaultValue + val = opt.defaultValue } if len(vs) > 0 { From ffcbe77b1e6222b4e0e97eb1920adc1813fb2224 Mon Sep 17 00:00:00 2001 From: Eason Lin Date: Sat, 6 Apr 2019 21:48:33 +0800 Subject: [PATCH 262/303] chore(readme): rollback readme (#1846) #1844 #1838 Keep the documentation in readme until full available on the new website. --- README.md | 2010 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1972 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index d3433ed2..804041f9 100644 --- a/README.md +++ b/README.md @@ -13,35 +13,122 @@ Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. -**The key features of Gin are:** -- Zero allocation router -- Fast -- Middleware support -- Crash-free -- JSON validation -- Routes grouping -- Error management -- Rendering built-in -- Extendable +## Contents -For more feature details, please see the [Gin website introduction](https://gin-gonic.com/docs/introduction/). +- [Installation](#installation) +- [Prerequisite](#prerequisite) +- [Quick start](#quick-start) +- [Benchmarks](#benchmarks) +- [Gin v1.stable](#gin-v1-stable) +- [Build with jsoniter](#build-with-jsoniter) +- [API Examples](#api-examples) + - [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) + - [Parameters in path](#parameters-in-path) + - [Querystring parameters](#querystring-parameters) + - [Multipart/Urlencoded Form](#multiparturlencoded-form) + - [Another example: query + post form](#another-example-query--post-form) + - [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters) + - [Upload files](#upload-files) + - [Grouping routes](#grouping-routes) + - [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default) + - [Using middleware](#using-middleware) + - [How to write log file](#how-to-write-log-file) + - [Custom Log Format](#custom-log-format) + - [Model binding and validation](#model-binding-and-validation) + - [Custom Validators](#custom-validators) + - [Only Bind Query String](#only-bind-query-string) + - [Bind Query String or Post Data](#bind-query-string-or-post-data) + - [Bind Uri](#bind-uri) + - [Bind HTML checkboxes](#bind-html-checkboxes) + - [Multipart/Urlencoded binding](#multiparturlencoded-binding) + - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) + - [JSONP rendering](#jsonp) + - [Serving static files](#serving-static-files) + - [Serving data from reader](#serving-data-from-reader) + - [HTML rendering](#html-rendering) + - [Multitemplate](#multitemplate) + - [Redirects](#redirects) + - [Custom Middleware](#custom-middleware) + - [Using BasicAuth() middleware](#using-basicauth-middleware) + - [Goroutines inside a middleware](#goroutines-inside-a-middleware) + - [Custom HTTP configuration](#custom-http-configuration) + - [Support Let's Encrypt](#support-lets-encrypt) + - [Run multiple service using Gin](#run-multiple-service-using-gin) + - [Graceful restart or stop](#graceful-restart-or-stop) + - [Build a single binary with templates](#build-a-single-binary-with-templates) + - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) + - [Try to bind body into different structs](#try-to-bind-body-into-different-structs) + - [http2 server push](#http2-server-push) + - [Define format for the log of routes](#define-format-for-the-log-of-routes) + - [Set and get a cookie](#set-and-get-a-cookie) +- [Testing](#testing) +- [Users](#users) -## Getting started +## Installation -### Getting Gin +To install Gin package, you need to install Go and set your Go workspace first. -The first need [Go](https://golang.org/) installed (**version 1.6+ is required**), then you can use the below Go command to install Gin. +1. Download and install it: ```sh $ go get -u github.com/gin-gonic/gin ``` -For more installation guides such as vendor tool, please check out [Gin quickstart](https://gin-gonic.com/docs/quickstart/). +2. Import it in your code: -### Running Gin +```go +import "github.com/gin-gonic/gin" +``` -First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`: +3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`. + +```go +import "net/http" +``` + +### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor) + +1. `go get` govendor + +```sh +$ go get github.com/kardianos/govendor +``` +2. Create your project folder and `cd` inside + +```sh +$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" +``` + +3. Vendor init your project and add gin + +```sh +$ govendor init +$ govendor fetch github.com/gin-gonic/gin@v1.3 +``` + +4. Copy a starting template inside your project + +```sh +$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go +``` + +5. Run your project + +```sh +$ go run main.go +``` + +## Prerequisite + +Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. + +## Quick start + +```sh +# assume the following codes in example.go file +$ cat example.go +``` ```go package main @@ -59,8 +146,6 @@ func main() { } ``` -And use the Go command to run the demo: - ``` # run example.go and visit 0.0.0.0:8080/ping on browser $ go run example.go @@ -68,7 +153,9 @@ $ go run example.go ## Benchmarks -Please see all benchmarks details from [Gin website](https://gin-gonic.com/docs/benchmarks/). +Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) + +[See all benchmarks](/BENCHMARKS.md) Benchmark name | (1) | (2) | (3) | (4) --------------------------------------------|-----------:|------------:|-----------:|---------: @@ -105,32 +192,1879 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 - (3): Heap Memory (B/op), lower is better - (4): Average Allocations per Repetition (allocs/op), lower is better -## Middlewares +## Gin v1. stable -You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib). +- [x] Zero allocation router. +- [x] Still the fastest http router and framework. From routing to writing. +- [x] Complete suite of unit tests +- [x] Battle tested +- [x] API frozen, new releases will not break your code. -## Documentation +## Build with [jsoniter](https://github.com/json-iterator/go) -See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package. +Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. -All documentation is available on the Gin website. +```sh +$ go build -tags=jsoniter . +``` -- [English](https://gin-gonic.com/docs/) -- [简体中文](https://gin-gonic.com/zh-cn/docs/) -- [繁體中文](https://gin-gonic.com/zh-tw/docs/) -- [日本語](https://gin-gonic.com/ja/docs/) +## API Examples -## Examples +You can find a number of ready-to-run examples at [Gin examples repository](https://github.com/gin-gonic/examples). -A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository. +### Using GET, POST, PUT, PATCH, DELETE and OPTIONS + +```go +func main() { + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/someGet", getting) + router.POST("/somePost", posting) + router.PUT("/somePut", putting) + router.DELETE("/someDelete", deleting) + router.PATCH("/somePatch", patching) + router.HEAD("/someHead", head) + router.OPTIONS("/someOptions", options) + + // By default it serves on :8080 unless a + // PORT environment variable was defined. + router.Run() + // router.Run(":3000") for a hard coded port +} +``` + +### Parameters in path + +```go +func main() { + router := gin.Default() + + // This handler will match /user/john but will not match /user/ or /user + router.GET("/user/:name", func(c *gin.Context) { + name := c.Param("name") + c.String(http.StatusOK, "Hello %s", name) + }) + + // However, this one will match /user/john/ and also /user/john/send + // If no other routers match /user/john, it will redirect to /user/john/ + router.GET("/user/:name/*action", func(c *gin.Context) { + name := c.Param("name") + action := c.Param("action") + message := name + " is " + action + c.String(http.StatusOK, message) + }) + + router.Run(":8080") +} +``` + +### Querystring parameters + +```go +func main() { + router := gin.Default() + + // Query string parameters are parsed using the existing underlying request object. + // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe + router.GET("/welcome", func(c *gin.Context) { + firstname := c.DefaultQuery("firstname", "Guest") + lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname") + + c.String(http.StatusOK, "Hello %s %s", firstname, lastname) + }) + router.Run(":8080") +} +``` + +### Multipart/Urlencoded Form + +```go +func main() { + router := gin.Default() + + router.POST("/form_post", func(c *gin.Context) { + message := c.PostForm("message") + nick := c.DefaultPostForm("nick", "anonymous") + + c.JSON(200, gin.H{ + "status": "posted", + "message": message, + "nick": nick, + }) + }) + router.Run(":8080") +} +``` + +### Another example: query + post form + +``` +POST /post?id=1234&page=1 HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +name=manu&message=this_is_great +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + id := c.Query("id") + page := c.DefaultQuery("page", "0") + name := c.PostForm("name") + message := c.PostForm("message") + + fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) + }) + router.Run(":8080") +} +``` + +``` +id: 1234; page: 1; name: manu; message: this_is_great +``` + +### Map as querystring or postform parameters + +``` +POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +names[first]=thinkerou&names[second]=tianou +``` + +```go +func main() { + router := gin.Default() + + router.POST("/post", func(c *gin.Context) { + + ids := c.QueryMap("ids") + names := c.PostFormMap("names") + + fmt.Printf("ids: %v; names: %v", ids, names) + }) + router.Run(":8080") +} +``` + +``` +ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] +``` + +### Upload files + +#### Single file + +References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single). + +`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://github.com/gin-gonic/gin/issues/1693) + +> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done. + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + // router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // single file + file, _ := c.FormFile("file") + log.Println(file.Filename) + + // Upload the file to specific dst. + // c.SaveUploadedFile(file, dst) + + c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "file=@/Users/appleboy/test.zip" \ + -H "Content-Type: multipart/form-data" +``` + +#### Multiple files + +See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple). + +```go +func main() { + router := gin.Default() + // Set a lower memory limit for multipart forms (default is 32 MiB) + // router.MaxMultipartMemory = 8 << 20 // 8 MiB + router.POST("/upload", func(c *gin.Context) { + // Multipart form + form, _ := c.MultipartForm() + files := form.File["upload[]"] + + for _, file := range files { + log.Println(file.Filename) + + // Upload the file to specific dst. + // c.SaveUploadedFile(file, dst) + } + c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) + }) + router.Run(":8080") +} +``` + +How to `curl`: + +```bash +curl -X POST http://localhost:8080/upload \ + -F "upload[]=@/Users/appleboy/test1.zip" \ + -F "upload[]=@/Users/appleboy/test2.zip" \ + -H "Content-Type: multipart/form-data" +``` + +### Grouping routes + +```go +func main() { + router := gin.Default() + + // Simple group: v1 + v1 := router.Group("/v1") + { + v1.POST("/login", loginEndpoint) + v1.POST("/submit", submitEndpoint) + v1.POST("/read", readEndpoint) + } + + // Simple group: v2 + v2 := router.Group("/v2") + { + v2.POST("/login", loginEndpoint) + v2.POST("/submit", submitEndpoint) + v2.POST("/read", readEndpoint) + } + + router.Run(":8080") +} +``` + +### Blank Gin without middleware by default + +Use + +```go +r := gin.New() +``` + +instead of + +```go +// Default With the Logger and Recovery middleware already attached +r := gin.Default() +``` + + +### Using middleware +```go +func main() { + // Creates a router without any middleware by default + r := gin.New() + + // Global middleware + // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release. + // By default gin.DefaultWriter = os.Stdout + r.Use(gin.Logger()) + + // Recovery middleware recovers from any panics and writes a 500 if there was one. + r.Use(gin.Recovery()) + + // Per route middleware, you can add as many as you desire. + r.GET("/benchmark", MyBenchLogger(), benchEndpoint) + + // Authorization group + // authorized := r.Group("/", AuthRequired()) + // exactly the same as: + authorized := r.Group("/") + // per group middleware! in this case we use the custom created + // AuthRequired() middleware just in the "authorized" group. + authorized.Use(AuthRequired()) + { + authorized.POST("/login", loginEndpoint) + authorized.POST("/submit", submitEndpoint) + authorized.POST("/read", readEndpoint) + + // nested group + testing := authorized.Group("testing") + testing.GET("/analytics", analyticsEndpoint) + } + + // Listen and serve on 0.0.0.0:8080 + r.Run(":8080") +} +``` + +### How to write log file +```go +func main() { + // Disable Console Color, you don't need console color when writing the logs to file. + gin.DisableConsoleColor() + + // Logging to a file. + f, _ := os.Create("gin.log") + gin.DefaultWriter = io.MultiWriter(f) + + // Use the following code if you need to write the logs to file and console at the same time. + // gin.DefaultWriter = io.MultiWriter(f, os.Stdout) + + router := gin.Default() + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + +    router.Run(":8080") +} +``` + +### Custom Log Format +```go +func main() { + router := gin.New() + + // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter + // By default gin.DefaultWriter = os.Stdout + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + + // your custom format + return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n", + param.ClientIP, + param.TimeStamp.Format(time.RFC1123), + param.Method, + param.Path, + param.Request.Proto, + param.StatusCode, + param.Latency, + param.Request.UserAgent(), + param.ErrorMessage, + ) + })) + router.Use(gin.Recovery()) + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +**Sample Output** +``` +::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " +``` + +### Controlling Log output coloring + +By default, logs output on console should be colorized depending on the detected TTY. + +Never colorize logs: + +```go +func main() { + // Disable log's color + gin.DisableConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +Always colorize logs: + +```go +func main() { + // Force log's color + gin.ForceConsoleColor() + + // Creates a gin router with default middleware: + // logger and recovery (crash-free) middleware + router := gin.Default() + + router.GET("/ping", func(c *gin.Context) { + c.String(200, "pong") + }) + + router.Run(":8080") +} +``` + +### Model binding and validation + +To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). + +Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags). + +Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`. + +Also, Gin provides two sets of methods for binding: +- **Type** - Must bind + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` + - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. +- **Type** - Should bind + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML` + - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. + +When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. + +You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned. + +```go +// Binding from JSON +type Login struct { + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` +} + +func main() { + router := gin.Default() + + // Example for binding JSON ({"user": "manu", "password": "123"}) + router.POST("/loginJSON", func(c *gin.Context) { + var json Login + if err := c.ShouldBindJSON(&json); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if json.User != "manu" || json.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding XML ( + // + // + // user + // 123 + // ) + router.POST("/loginXML", func(c *gin.Context) { + var xml Login + if err := c.ShouldBindXML(&xml); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if xml.User != "manu" || xml.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Example for binding a HTML form (user=manu&password=123) + router.POST("/loginForm", func(c *gin.Context) { + var form Login + // This will infer what binder to use depending on the content-type header. + if err := c.ShouldBind(&form); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if form.User != "manu" || form.Password != "123" { + c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) + }) + + // Listen and serve on 0.0.0.0:8080 + router.Run(":8080") +} +``` + +**Sample request** +```shell +$ curl -v -X POST \ + http://localhost:8080/loginJSON \ + -H 'content-type: application/json' \ + -d '{ "user": "manu" }' +> POST /loginJSON HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.51.0 +> Accept: */* +> content-type: application/json +> Content-Length: 18 +> +* upload completely sent off: 18 out of 18 bytes +< HTTP/1.1 400 Bad Request +< Content-Type: application/json; charset=utf-8 +< Date: Fri, 04 Aug 2017 03:51:31 GMT +< Content-Length: 100 +< +{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"} +``` + +**Skip validate** + +When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again. + +### Custom Validators + +It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/custom-validation/server.go). + +```go +package main + +import ( + "net/http" + "reflect" + "time" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "gopkg.in/go-playground/validator.v8" +) + +// Booking contains binded and validated data. +type Booking struct { + CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` + CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` +} + +func bookableDate( + v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, + field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string, +) bool { + if date, ok := field.Interface().(time.Time); ok { + today := time.Now() + if today.Year() > date.Year() || today.YearDay() > date.YearDay() { + return false + } + } + return true +} + +func main() { + route := gin.Default() + + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + v.RegisterValidation("bookabledate", bookableDate) + } + + route.GET("/bookable", getBookable) + route.Run(":8085") +} + +func getBookable(c *gin.Context) { + var b Booking + if err := c.ShouldBindWith(&b, binding.Query); err == nil { + c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"}) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } +} +``` + +```console +$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17" +{"message":"Booking dates are valid!"} + +$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09" +{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} +``` + +[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way. +See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more. + +### Only Bind Query String + +`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017). + +```go +package main + +import ( + "log" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` +} + +func main() { + route := gin.Default() + route.Any("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + if c.ShouldBindQuery(&person) == nil { + log.Println("====== Only Bind By Query String ======") + log.Println(person.Name) + log.Println(person.Address) + } + c.String(200, "Success") +} + +``` + +### Bind Query String or Post Data + +See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-264681292). + +```go +package main + +import ( + "log" + "time" + + "github.com/gin-gonic/gin" +) + +type Person struct { + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` +} + +func main() { + route := gin.Default() + route.GET("/testing", startPage) + route.Run(":8085") +} + +func startPage(c *gin.Context) { + var person Person + // If `GET`, only `Form` binding engine (`query`) used. + // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). + // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + log.Println(person.Birthday) + } + + c.String(200, "Success") +} +``` + +Test it with: +```sh +$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" +``` + +### Bind Uri + +See the [detail information](https://github.com/gin-gonic/gin/issues/846). + +```go +package main + +import "github.com/gin-gonic/gin" + +type Person struct { + ID string `uri:"id" binding:"required,uuid"` + Name string `uri:"name" binding:"required"` +} + +func main() { + route := gin.Default() + route.GET("/:name/:id", func(c *gin.Context) { + var person Person + if err := c.ShouldBindUri(&person); err != nil { + c.JSON(400, gin.H{"msg": err}) + return + } + c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) + }) + route.Run(":8088") +} +``` + +Test it with: +```sh +$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 +$ curl -v localhost:8088/thinkerou/not-uuid +``` + +### Bind HTML checkboxes + +See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) + +main.go + +```go +... + +type myForm struct { + Colors []string `form:"colors[]"` +} + +... + +func formHandler(c *gin.Context) { + var fakeForm myForm + c.ShouldBind(&fakeForm) + c.JSON(200, gin.H{"color": fakeForm.Colors}) +} + +... + +``` + +form.html + +```html +
+

Check some colors

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

+ {{ .title }} +

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

+ {{ .title }} +

+

Using posts/index.tmpl

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

+ {{ .title }} +

+

Using users/index.tmpl

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

Welcome, Ginner!

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

Welcome, Ginner!

+ + +`)) + + router := New() + router.Static("./assets", "./assets") + router.SetHTMLTemplate(html) + + go func() { + router.GET("/pusher", func(c *Context) { + if pusher := c.Writer.Pusher(); pusher != nil { + pusher.Push("/assets/app.js", nil) + } + c.String(http.StatusOK, "it worked") + }) + + assert.NoError(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + }() + + // have to wait for the goroutine to start and run the server + // otherwise the main thread will complete + time.Sleep(5 * time.Millisecond) + + assert.Error(t, router.RunTLS(":8449", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem")) + testRequest(t, "https://localhost:8449/pusher") +} + func TestRunEmptyWithEnv(t *testing.T) { os.Setenv("PORT", "3123") router := New() diff --git a/recovery_test.go b/recovery_test.go index e1a0713f..21a0a480 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -// +build go1.7 - package gin import ( diff --git a/render/json.go b/render/json.go index c7cf330e..18f27fa9 100644 --- a/render/json.go +++ b/render/json.go @@ -43,6 +43,11 @@ type AsciiJSON struct { // SecureJSONPrefix is a string which represents SecureJSON prefix. type SecureJSONPrefix string +// PureJSON contains the given interface object. +type PureJSON struct { + Data interface{} +} + var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} var jsonAsciiContentType = []string{"application/json"} @@ -174,3 +179,16 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonAsciiContentType) } + +// Render (PureJSON) writes custom ContentType and encodes the given interface object. +func (r PureJSON) Render(w http.ResponseWriter) error { + r.WriteContentType(w) + encoder := json.NewEncoder(w) + encoder.SetEscapeHTML(false) + return encoder.Encode(r.Data) +} + +// WriteContentType (PureJSON) writes custom ContentType. +func (r PureJSON) WriteContentType(w http.ResponseWriter) { + writeContentType(w, jsonContentType) +} diff --git a/render/json_17.go b/render/json_17.go deleted file mode 100644 index 208193c7..00000000 --- a/render/json_17.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build go1.7 - -package render - -import ( - "net/http" - - "github.com/gin-gonic/gin/internal/json" -) - -// PureJSON contains the given interface object. -type PureJSON struct { - Data interface{} -} - -// Render (PureJSON) writes custom ContentType and encodes the given interface object. -func (r PureJSON) Render(w http.ResponseWriter) error { - r.WriteContentType(w) - encoder := json.NewEncoder(w) - encoder.SetEscapeHTML(false) - return encoder.Encode(r.Data) -} - -// WriteContentType (PureJSON) writes custom ContentType. -func (r PureJSON) WriteContentType(w http.ResponseWriter) { - writeContentType(w, jsonContentType) -} diff --git a/render/redirect.go b/render/redirect.go index 9c145fe2..c006691c 100644 --- a/render/redirect.go +++ b/render/redirect.go @@ -18,9 +18,7 @@ type Redirect struct { // Render (Redirect) redirects the http request to new location and writes redirect response. func (r Redirect) Render(w http.ResponseWriter) error { - // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) - // when we upgrade go version we can use http.StatusPermanentRedirect - if (r.Code < 300 || r.Code > 308) && r.Code != 201 { + if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated { panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code)) } http.Redirect(w, r.Request, r.Location, r.Code) diff --git a/render/render_17_test.go b/render/render_17_test.go deleted file mode 100644 index 68330090..00000000 --- a/render/render_17_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -// +build go1.7 - -package render - -import ( - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRenderPureJSON(t *testing.T) { - w := httptest.NewRecorder() - data := map[string]interface{}{ - "foo": "bar", - "html": "", - } - err := (PureJSON{data}).Render(w) - assert.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) - assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) -} diff --git a/render/render_test.go b/render/render_test.go index 76e29eeb..3aa5dbcc 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -215,6 +215,18 @@ func TestRenderAsciiJSONFail(t *testing.T) { assert.Error(t, (AsciiJSON{data}).Render(w)) } +func TestRenderPureJSON(t *testing.T) { + w := httptest.NewRecorder() + data := map[string]interface{}{ + "foo": "bar", + "html": "", + } + err := (PureJSON{data}).Render(w) + assert.NoError(t, err) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) + assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) +} + type xmlmap map[string]interface{} // Allows type H to be used with xml.Marshal diff --git a/response_writer.go b/response_writer.go index 923b53f8..26826689 100644 --- a/response_writer.go +++ b/response_writer.go @@ -16,7 +16,8 @@ const ( defaultStatus = http.StatusOK ) -type responseWriterBase interface { +// ResponseWriter ... +type ResponseWriter interface { http.ResponseWriter http.Hijacker http.Flusher @@ -37,6 +38,9 @@ type responseWriterBase interface { // Forces to write the http header (status code + headers). WriteHeaderNow() + + // get the http.Pusher for server push + Pusher() http.Pusher } type responseWriter struct { @@ -113,3 +117,10 @@ func (w *responseWriter) Flush() { w.WriteHeaderNow() w.ResponseWriter.(http.Flusher).Flush() } + +func (w *responseWriter) Pusher() (pusher http.Pusher) { + if pusher, ok := w.ResponseWriter.(http.Pusher); ok { + return pusher + } + return nil +} diff --git a/response_writer_1.7.go b/response_writer_1.7.go deleted file mode 100644 index 801d196b..00000000 --- a/response_writer_1.7.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build !go1.8 - -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package gin - -// ResponseWriter ... -type ResponseWriter interface { - responseWriterBase -} diff --git a/response_writer_1.8.go b/response_writer_1.8.go deleted file mode 100644 index 527c0038..00000000 --- a/response_writer_1.8.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build go1.8 - -// Copyright 2018 Gin Core Team. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package gin - -import ( - "net/http" -) - -// ResponseWriter ... -type ResponseWriter interface { - responseWriterBase - // get the http.Pusher for server push - Pusher() http.Pusher -} - -func (w *responseWriter) Pusher() (pusher http.Pusher) { - if pusher, ok := w.ResponseWriter.(http.Pusher); ok { - return pusher - } - return nil -} diff --git a/vendor/vendor.json b/vendor/vendor.json index 6050e8f6..fc7bb11d 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1,5 +1,5 @@ { - "comment": "v1.3.0", + "comment": "v1.4.0", "ignore": "test", "package": [ { @@ -13,16 +13,16 @@ { "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", "path": "github.com/gin-contrib/sse", - "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", - "revisionTime": "2017-01-09T09:34:21Z" + "revision": "5545eab6dad3bbbd6c5ae9186383c2a9d23c0dae", + "revisionTime": "2019-03-01T06:25:29Z" }, { - "checksumSHA1": "mE9XW26JSpe4meBObM6J/Oeq0eg=", + "checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=", "path": "github.com/golang/protobuf/proto", - "revision": "aa810b61a9c79d51363740d207bb46cf8e620ed5", - "revisionTime": "2018-08-14T21:14:27Z", - "version": "v1.2", - "versionExact": "v1.2.0" + "revision": "c823c79ea1570fb5ff454033735a8e68575d1d0f", + "revisionTime": "2019-02-05T22:20:52Z", + "version": "v1.3", + "versionExact": "v1.3.0" }, { "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=", @@ -33,12 +33,24 @@ "versionExact": "v1.1.5" }, { - "checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=", + "checksumSHA1": "rrXDDvz+nQ2KRLQk6nxWaE5Zj1U=", "path": "github.com/mattn/go-isatty", - "revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c", - "revisionTime": "2017-11-07T05:05:31Z", + "revision": "369ecd8cea9851e459abb67eb171853e3986591e", + "revisionTime": "2019-02-25T17:38:24Z", "version": "v0.0", - "versionExact": "v0.0.4" + "versionExact": "v0.0.6" + }, + { + "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", + "path": "github.com/modern-go/concurrent", + "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", + "revisionTime": "2018-03-06T01:26:44Z" + }, + { + "checksumSHA1": "qvH48wzTIV3QKSDqI0dLFtVjaDI=", + "path": "github.com/modern-go/reflect2", + "revision": "94122c33edd36123c84d5368cfb2b69df93a0ec8", + "revisionTime": "2018-07-18T01:23:57Z" }, { "checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=", @@ -46,6 +58,20 @@ "revision": "5d4384ee4fb2527b0a1256a821ebfc92f91efefc", "revisionTime": "2018-12-26T10:54:42Z" }, + { + "checksumSHA1": "cpNsoLqBprpKh+VZTBOZNVXzBEk=", + "path": "github.com/stretchr/objx", + "revision": "c61a9dfcced1815e7d40e214d00d1a8669a9f58c", + "revisionTime": "2019-02-11T16:23:28Z" + }, + { + "checksumSHA1": "DBdcVxnvaINHhWyyGgih/Mel6gE=", + "path": "github.com/stretchr/testify", + "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", + "revisionTime": "2018-12-05T02:12:43Z", + "version": "v1.3", + "versionExact": "v1.3.0" + }, { "checksumSHA1": "c6pbpF7eowwO59phRTpF8cQ80Z0=", "path": "github.com/stretchr/testify/assert", @@ -55,12 +81,26 @@ "versionExact": "v1.2.2" }, { - "checksumSHA1": "5Bd8RPhhaKcEXkagzPqymP4Gx5E=", + "checksumSHA1": "fg3TzS9/QK3wZbzei3Z6O8XPLHg=", + "path": "github.com/stretchr/testify/http", + "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", + "revisionTime": "2018-12-05T02:12:43Z", + "version": "v1.3", + "versionExact": "v1.3.0" + }, + { + "checksumSHA1": "lsdl3fgOiM4Iuy7xjTQxiBtAwB0=", + "path": "github.com/stretchr/testify/mock", + "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", + "revisionTime": "2018-12-05T02:12:43Z", + "version": "v1.3", + "versionExact": "v1.3.0" + }, + { + "checksumSHA1": "WIhpR3EKGueRSJsYOZ6PIsfL4SI=", "path": "github.com/ugorji/go/codec", - "revision": "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab", - "revisionTime": "2018-04-07T10:07:33Z", - "version": "v1.1", - "versionExact": "v1.1.1" + "revision": "e444a5086c436778cf9281a7059a3d58b9e17935", + "revisionTime": "2019-02-04T20:13:41Z" }, { "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", @@ -83,13 +123,13 @@ "versionExact": "v8.18.2" }, { - "checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=", + "checksumSHA1": "QqDq2x8XOU7IoOR98Cx1eiV5QY8=", "path": "gopkg.in/yaml.v2", - "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", - "revisionTime": "2018-03-28T19:50:20Z", + "revision": "51d6538a90f86fe93ac480b35f37b2be17fef232", + "revisionTime": "2018-11-15T11:05:04Z", "version": "v2.2", - "versionExact": "v2.2.1" + "versionExact": "v2.2.2" } ], "rootPath": "github.com/gin-gonic/gin" -} +} \ No newline at end of file diff --git a/version.go b/version.go index 028caebe..07e7859f 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.4.0-dev" +const Version = "v1.4.0" From 66d2c30c54ff8042f5ae13d9ebb26dfe556561fe Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Tue, 7 May 2019 14:06:55 +0300 Subject: [PATCH 267/303] binding: move tests of mapping to separate test file (#1842) * move tests of mapping to separate test file make 100% coverage of form_mapping.go from form_mapping_test.go file * fix tests for go 1.6 go 1.6 doesn't support `t.Run(...)` subtests --- binding/binding_test.go | 406 ----------------------------------- binding/form_mapping_test.go | 271 +++++++++++++++++++++++ 2 files changed, 271 insertions(+), 406 deletions(-) create mode 100644 binding/form_mapping_test.go diff --git a/binding/binding_test.go b/binding/binding_test.go index ee788225..73bb7700 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -114,71 +114,6 @@ type FooStructForBoolType struct { BoolFoo bool `form:"bool_foo"` } -type FooBarStructForIntType struct { - IntFoo int `form:"int_foo"` - IntBar int `form:"int_bar" binding:"required"` -} - -type FooBarStructForInt8Type struct { - Int8Foo int8 `form:"int8_foo"` - Int8Bar int8 `form:"int8_bar" binding:"required"` -} - -type FooBarStructForInt16Type struct { - Int16Foo int16 `form:"int16_foo"` - Int16Bar int16 `form:"int16_bar" binding:"required"` -} - -type FooBarStructForInt32Type struct { - Int32Foo int32 `form:"int32_foo"` - Int32Bar int32 `form:"int32_bar" binding:"required"` -} - -type FooBarStructForInt64Type struct { - Int64Foo int64 `form:"int64_foo"` - Int64Bar int64 `form:"int64_bar" binding:"required"` -} - -type FooBarStructForUintType struct { - UintFoo uint `form:"uint_foo"` - UintBar uint `form:"uint_bar" binding:"required"` -} - -type FooBarStructForUint8Type struct { - Uint8Foo uint8 `form:"uint8_foo"` - Uint8Bar uint8 `form:"uint8_bar" binding:"required"` -} - -type FooBarStructForUint16Type struct { - Uint16Foo uint16 `form:"uint16_foo"` - Uint16Bar uint16 `form:"uint16_bar" binding:"required"` -} - -type FooBarStructForUint32Type struct { - Uint32Foo uint32 `form:"uint32_foo"` - Uint32Bar uint32 `form:"uint32_bar" binding:"required"` -} - -type FooBarStructForUint64Type struct { - Uint64Foo uint64 `form:"uint64_foo"` - Uint64Bar uint64 `form:"uint64_bar" binding:"required"` -} - -type FooBarStructForBoolType struct { - BoolFoo bool `form:"bool_foo"` - BoolBar bool `form:"bool_bar" binding:"required"` -} - -type FooBarStructForFloat32Type struct { - Float32Foo float32 `form:"float32_foo"` - Float32Bar float32 `form:"float32_bar" binding:"required"` -} - -type FooBarStructForFloat64Type struct { - Float64Foo float64 `form:"float64_foo"` - Float64Bar float64 `form:"float64_bar" binding:"required"` -} - type FooStructForStringPtrType struct { PtrFoo *string `form:"ptr_foo"` PtrBar *string `form:"ptr_bar" binding:"required"` @@ -335,110 +270,6 @@ func TestBindingFormForType(t *testing.T) { "/?slice_map_foo=1&slice_map_foo=2", "/?bar2=1&bar2=2", "", "", "SliceMap") - testFormBindingForType(t, "POST", - "/", "/", - "int_foo=&int_bar=-12", "bar2=-123", "Int") - - testFormBindingForType(t, "GET", - "/?int_foo=&int_bar=-12", "/?bar2=-123", - "", "", "Int") - - testFormBindingForType(t, "POST", - "/", "/", - "int8_foo=&int8_bar=-12", "bar2=-123", "Int8") - - testFormBindingForType(t, "GET", - "/?int8_foo=&int8_bar=-12", "/?bar2=-123", - "", "", "Int8") - - testFormBindingForType(t, "POST", - "/", "/", - "int16_foo=&int16_bar=-12", "bar2=-123", "Int16") - - testFormBindingForType(t, "GET", - "/?int16_foo=&int16_bar=-12", "/?bar2=-123", - "", "", "Int16") - - testFormBindingForType(t, "POST", - "/", "/", - "int32_foo=&int32_bar=-12", "bar2=-123", "Int32") - - testFormBindingForType(t, "GET", - "/?int32_foo=&int32_bar=-12", "/?bar2=-123", - "", "", "Int32") - - testFormBindingForType(t, "POST", - "/", "/", - "int64_foo=&int64_bar=-12", "bar2=-123", "Int64") - - testFormBindingForType(t, "GET", - "/?int64_foo=&int64_bar=-12", "/?bar2=-123", - "", "", "Int64") - - testFormBindingForType(t, "POST", - "/", "/", - "uint_foo=&uint_bar=12", "bar2=123", "Uint") - - testFormBindingForType(t, "GET", - "/?uint_foo=&uint_bar=12", "/?bar2=123", - "", "", "Uint") - - testFormBindingForType(t, "POST", - "/", "/", - "uint8_foo=&uint8_bar=12", "bar2=123", "Uint8") - - testFormBindingForType(t, "GET", - "/?uint8_foo=&uint8_bar=12", "/?bar2=123", - "", "", "Uint8") - - testFormBindingForType(t, "POST", - "/", "/", - "uint16_foo=&uint16_bar=12", "bar2=123", "Uint16") - - testFormBindingForType(t, "GET", - "/?uint16_foo=&uint16_bar=12", "/?bar2=123", - "", "", "Uint16") - - testFormBindingForType(t, "POST", - "/", "/", - "uint32_foo=&uint32_bar=12", "bar2=123", "Uint32") - - testFormBindingForType(t, "GET", - "/?uint32_foo=&uint32_bar=12", "/?bar2=123", - "", "", "Uint32") - - testFormBindingForType(t, "POST", - "/", "/", - "uint64_foo=&uint64_bar=12", "bar2=123", "Uint64") - - testFormBindingForType(t, "GET", - "/?uint64_foo=&uint64_bar=12", "/?bar2=123", - "", "", "Uint64") - - testFormBindingForType(t, "POST", - "/", "/", - "bool_foo=&bool_bar=true", "bar2=true", "Bool") - - testFormBindingForType(t, "GET", - "/?bool_foo=&bool_bar=true", "/?bar2=true", - "", "", "Bool") - - testFormBindingForType(t, "POST", - "/", "/", - "float32_foo=&float32_bar=-12.34", "bar2=12.3", "Float32") - - testFormBindingForType(t, "GET", - "/?float32_foo=&float32_bar=-12.34", "/?bar2=12.3", - "", "", "Float32") - - testFormBindingForType(t, "POST", - "/", "/", - "float64_foo=&float64_bar=-12.34", "bar2=12.3", "Float64") - - testFormBindingForType(t, "GET", - "/?float64_foo=&float64_bar=-12.34", "/?bar2=12.3", - "", "", "Float64") - testFormBindingForType(t, "POST", "/", "/", "ptr_bar=test", "bar2=test", "Ptr") @@ -1076,149 +907,6 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s req.Header.Add("Content-Type", MIMEPOSTForm) } switch typ { - case "Int": - obj := FooBarStructForIntType{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int(0), obj.IntFoo) - assert.Equal(t, int(-12), obj.IntBar) - - obj = FooBarStructForIntType{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Int8": - obj := FooBarStructForInt8Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int8(0), obj.Int8Foo) - assert.Equal(t, int8(-12), obj.Int8Bar) - - obj = FooBarStructForInt8Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Int16": - obj := FooBarStructForInt16Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int16(0), obj.Int16Foo) - assert.Equal(t, int16(-12), obj.Int16Bar) - - obj = FooBarStructForInt16Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Int32": - obj := FooBarStructForInt32Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int32(0), obj.Int32Foo) - assert.Equal(t, int32(-12), obj.Int32Bar) - - obj = FooBarStructForInt32Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Int64": - obj := FooBarStructForInt64Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, int64(0), obj.Int64Foo) - assert.Equal(t, int64(-12), obj.Int64Bar) - - obj = FooBarStructForInt64Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint": - obj := FooBarStructForUintType{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint(0x0), obj.UintFoo) - assert.Equal(t, uint(0xc), obj.UintBar) - - obj = FooBarStructForUintType{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint8": - obj := FooBarStructForUint8Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint8(0x0), obj.Uint8Foo) - assert.Equal(t, uint8(0xc), obj.Uint8Bar) - - obj = FooBarStructForUint8Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint16": - obj := FooBarStructForUint16Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint16(0x0), obj.Uint16Foo) - assert.Equal(t, uint16(0xc), obj.Uint16Bar) - - obj = FooBarStructForUint16Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint32": - obj := FooBarStructForUint32Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint32(0x0), obj.Uint32Foo) - assert.Equal(t, uint32(0xc), obj.Uint32Bar) - - obj = FooBarStructForUint32Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Uint64": - obj := FooBarStructForUint64Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, uint64(0x0), obj.Uint64Foo) - assert.Equal(t, uint64(0xc), obj.Uint64Bar) - - obj = FooBarStructForUint64Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Float32": - obj := FooBarStructForFloat32Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float32(0.0), obj.Float32Foo) - assert.Equal(t, float32(-12.34), obj.Float32Bar) - - obj = FooBarStructForFloat32Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Float64": - obj := FooBarStructForFloat64Type{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.Equal(t, float64(0.0), obj.Float64Foo) - assert.Equal(t, float64(-12.34), obj.Float64Bar) - - obj = FooBarStructForFloat64Type{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) - case "Bool": - obj := FooBarStructForBoolType{} - err := b.Bind(req, &obj) - assert.NoError(t, err) - assert.False(t, obj.BoolFoo) - assert.True(t, obj.BoolBar) - - obj = FooBarStructForBoolType{} - req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) - assert.Error(t, err) case "Slice": obj := FooStructForSliceType{} err := b.Bind(req, &obj) @@ -1454,97 +1142,3 @@ func requestWithBody(method, path, body string) (req *http.Request) { req, _ = http.NewRequest(method, path, bytes.NewBufferString(body)) return } - -func TestCanSet(t *testing.T) { - type CanSetStruct struct { - lowerStart string `form:"lower"` - } - - var c CanSetStruct - assert.Nil(t, mapForm(&c, nil)) -} - -func formPostRequest(path, body string) *http.Request { - req := requestWithBody("POST", path, body) - req.Header.Add("Content-Type", MIMEPOSTForm) - return req -} - -func TestBindingSliceDefault(t *testing.T) { - var s struct { - Friends []string `form:"friends,default=mike"` - } - req := formPostRequest("", "") - err := Form.Bind(req, &s) - assert.NoError(t, err) - - assert.Len(t, s.Friends, 1) - assert.Equal(t, "mike", s.Friends[0]) -} - -func TestBindingStructField(t *testing.T) { - var s struct { - Opts struct { - Port int - } `form:"opts"` - } - req := formPostRequest("", `opts={"Port": 8000}`) - err := Form.Bind(req, &s) - assert.NoError(t, err) - assert.Equal(t, 8000, s.Opts.Port) -} - -func TestBindingUnknownTypeChan(t *testing.T) { - var s struct { - Stop chan bool `form:"stop"` - } - req := formPostRequest("", "stop=true") - err := Form.Bind(req, &s) - assert.Error(t, err) - assert.Equal(t, errUnknownType, err) -} - -func TestBindingTimeDuration(t *testing.T) { - var s struct { - Timeout time.Duration `form:"timeout"` - } - - // ok - req := formPostRequest("", "timeout=5s") - err := Form.Bind(req, &s) - assert.NoError(t, err) - assert.Equal(t, 5*time.Second, s.Timeout) - - // error - req = formPostRequest("", "timeout=wrong") - err = Form.Bind(req, &s) - assert.Error(t, err) -} - -func TestBindingArray(t *testing.T) { - var s struct { - Nums [2]int `form:"nums,default=4"` - } - - // default - req := formPostRequest("", "") - err := Form.Bind(req, &s) - assert.Error(t, err) - assert.Equal(t, [2]int{0, 0}, s.Nums) - - // ok - req = formPostRequest("", "nums=3&nums=8") - err = Form.Bind(req, &s) - assert.NoError(t, err) - assert.Equal(t, [2]int{3, 8}, s.Nums) - - // not enough vals - req = formPostRequest("", "nums=3") - err = Form.Bind(req, &s) - assert.Error(t, err) - - // error - req = formPostRequest("", "nums=3&nums=wrong") - err = Form.Bind(req, &s) - assert.Error(t, err) -} diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go new file mode 100644 index 00000000..c9d6111b --- /dev/null +++ b/binding/form_mapping_test.go @@ -0,0 +1,271 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestMappingBaseTypes(t *testing.T) { + intPtr := func(i int) *int { + return &i + } + for _, tt := range []struct { + name string + value interface{} + form string + expect interface{} + }{ + {"base type", struct{ F int }{}, "9", int(9)}, + {"base type", struct{ F int8 }{}, "9", int8(9)}, + {"base type", struct{ F int16 }{}, "9", int16(9)}, + {"base type", struct{ F int32 }{}, "9", int32(9)}, + {"base type", struct{ F int64 }{}, "9", int64(9)}, + {"base type", struct{ F uint }{}, "9", uint(9)}, + {"base type", struct{ F uint8 }{}, "9", uint8(9)}, + {"base type", struct{ F uint16 }{}, "9", uint16(9)}, + {"base type", struct{ F uint32 }{}, "9", uint32(9)}, + {"base type", struct{ F uint64 }{}, "9", uint64(9)}, + {"base type", struct{ F bool }{}, "True", true}, + {"base type", struct{ F float32 }{}, "9.1", float32(9.1)}, + {"base type", struct{ F float64 }{}, "9.1", float64(9.1)}, + {"base type", struct{ F string }{}, "test", string("test")}, + {"base type", struct{ F *int }{}, "9", intPtr(9)}, + + // zero values + {"zero value", struct{ F int }{}, "", int(0)}, + {"zero value", struct{ F uint }{}, "", uint(0)}, + {"zero value", struct{ F bool }{}, "", false}, + {"zero value", struct{ F float32 }{}, "", float32(0)}, + } { + tp := reflect.TypeOf(tt.value) + testName := tt.name + ":" + tp.Field(0).Type.String() + + val := reflect.New(reflect.TypeOf(tt.value)) + val.Elem().Set(reflect.ValueOf(tt.value)) + + field := val.Elem().Type().Field(0) + + _, err := mapping(val, emptyField, formSource{field.Name: {tt.form}}, "form") + assert.NoError(t, err, testName) + + actual := val.Elem().Field(0).Interface() + assert.Equal(t, tt.expect, actual, testName) + } +} + +func TestMappingDefault(t *testing.T) { + var s struct { + Int int `form:",default=9"` + Slice []int `form:",default=9"` + Array [1]int `form:",default=9"` + } + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) + + assert.Equal(t, 9, s.Int) + assert.Equal(t, []int{9}, s.Slice) + assert.Equal(t, [1]int{9}, s.Array) +} + +func TestMappingSkipField(t *testing.T) { + var s struct { + A int + } + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) + + assert.Equal(t, 0, s.A) +} + +func TestMappingIgnoreField(t *testing.T) { + var s struct { + A int `form:"A"` + B int `form:"-"` + } + err := mappingByPtr(&s, formSource{"A": {"9"}, "B": {"9"}}, "form") + assert.NoError(t, err) + + assert.Equal(t, 9, s.A) + assert.Equal(t, 0, s.B) +} + +func TestMappingUnexportedField(t *testing.T) { + var s struct { + A int `form:"a"` + b int `form:"b"` + } + err := mappingByPtr(&s, formSource{"a": {"9"}, "b": {"9"}}, "form") + assert.NoError(t, err) + + assert.Equal(t, 9, s.A) + assert.Equal(t, 0, s.b) +} + +func TestMappingPrivateField(t *testing.T) { + var s struct { + f int `form:"field"` + } + err := mappingByPtr(&s, formSource{"field": {"6"}}, "form") + assert.NoError(t, err) + assert.Equal(t, int(0), s.f) +} + +func TestMappingUnknownFieldType(t *testing.T) { + var s struct { + U uintptr + } + + err := mappingByPtr(&s, formSource{"U": {"unknown"}}, "form") + assert.Error(t, err) + assert.Equal(t, errUnknownType, err) +} + +func TestMappingURI(t *testing.T) { + var s struct { + F int `uri:"field"` + } + err := mapUri(&s, map[string][]string{"field": {"6"}}) + assert.NoError(t, err) + assert.Equal(t, int(6), s.F) +} + +func TestMappingForm(t *testing.T) { + var s struct { + F int `form:"field"` + } + err := mapForm(&s, map[string][]string{"field": {"6"}}) + assert.NoError(t, err) + assert.Equal(t, int(6), s.F) +} + +func TestMappingTime(t *testing.T) { + var s struct { + Time time.Time + LocalTime time.Time `time_format:"2006-01-02"` + ZeroValue time.Time + CSTTime time.Time `time_format:"2006-01-02" time_location:"Asia/Shanghai"` + UTCTime time.Time `time_format:"2006-01-02" time_utc:"1"` + } + + var err error + time.Local, err = time.LoadLocation("Europe/Berlin") + assert.NoError(t, err) + + err = mapForm(&s, map[string][]string{ + "Time": {"2019-01-20T16:02:58Z"}, + "LocalTime": {"2019-01-20"}, + "ZeroValue": {}, + "CSTTime": {"2019-01-20"}, + "UTCTime": {"2019-01-20"}, + }) + assert.NoError(t, err) + + assert.Equal(t, "2019-01-20 16:02:58 +0000 UTC", s.Time.String()) + assert.Equal(t, "2019-01-20 00:00:00 +0100 CET", s.LocalTime.String()) + assert.Equal(t, "2019-01-19 23:00:00 +0000 UTC", s.LocalTime.UTC().String()) + assert.Equal(t, "0001-01-01 00:00:00 +0000 UTC", s.ZeroValue.String()) + assert.Equal(t, "2019-01-20 00:00:00 +0800 CST", s.CSTTime.String()) + assert.Equal(t, "2019-01-19 16:00:00 +0000 UTC", s.CSTTime.UTC().String()) + assert.Equal(t, "2019-01-20 00:00:00 +0000 UTC", s.UTCTime.String()) + + // wrong location + var wrongLoc struct { + Time time.Time `time_location:"wrong"` + } + err = mapForm(&wrongLoc, map[string][]string{"Time": {"2019-01-20T16:02:58Z"}}) + assert.Error(t, err) + + // wrong time value + var wrongTime struct { + Time time.Time + } + err = mapForm(&wrongTime, map[string][]string{"Time": {"wrong"}}) + assert.Error(t, err) +} + +func TestMapiingTimeDuration(t *testing.T) { + var s struct { + D time.Duration + } + + // ok + err := mappingByPtr(&s, formSource{"D": {"5s"}}, "form") + assert.NoError(t, err) + assert.Equal(t, 5*time.Second, s.D) + + // error + err = mappingByPtr(&s, formSource{"D": {"wrong"}}, "form") + assert.Error(t, err) +} + +func TestMappingSlice(t *testing.T) { + var s struct { + Slice []int `form:"slice,default=9"` + } + + // default value + err := mappingByPtr(&s, formSource{}, "form") + assert.NoError(t, err) + assert.Equal(t, []int{9}, s.Slice) + + // ok + err = mappingByPtr(&s, formSource{"slice": {"3", "4"}}, "form") + assert.NoError(t, err) + assert.Equal(t, []int{3, 4}, s.Slice) + + // error + err = mappingByPtr(&s, formSource{"slice": {"wrong"}}, "form") + assert.Error(t, err) +} + +func TestMappingArray(t *testing.T) { + var s struct { + Array [2]int `form:"array,default=9"` + } + + // wrong default + err := mappingByPtr(&s, formSource{}, "form") + assert.Error(t, err) + + // ok + err = mappingByPtr(&s, formSource{"array": {"3", "4"}}, "form") + assert.NoError(t, err) + assert.Equal(t, [2]int{3, 4}, s.Array) + + // error - not enough vals + err = mappingByPtr(&s, formSource{"array": {"3"}}, "form") + assert.Error(t, err) + + // error - wrong value + err = mappingByPtr(&s, formSource{"array": {"wrong"}}, "form") + assert.Error(t, err) +} + +func TestMappingStructField(t *testing.T) { + var s struct { + J struct { + I int + } + } + + err := mappingByPtr(&s, formSource{"J": {`{"I": 9}`}}, "form") + assert.NoError(t, err) + assert.Equal(t, 9, s.J.I) +} + +func TestMappingMapField(t *testing.T) { + var s struct { + M map[string]int + } + + err := mappingByPtr(&s, formSource{"M": {`{"one": 1}`}}, "form") + assert.NoError(t, err) + assert.Equal(t, map[string]int{"one": 1}, s.M) +} From b6425689dc657ad20762ada1591ebcc50f668c09 Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Tue, 7 May 2019 04:32:35 -0700 Subject: [PATCH 268/303] Clean the Request Path early (#1817) This will reduce the number of times we have todo a redirect. and allow multiple slashes in path to be routed! fixes #1644 --- gin.go | 1 + routes_test.go | 52 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/gin.go b/gin.go index 2d24092f..4dbe9836 100644 --- a/gin.go +++ b/gin.go @@ -372,6 +372,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { rPath = c.Request.URL.RawPath unescape = engine.UnescapePathValues } + rPath = cleanPath(rPath) // Find root of the tree for the given HTTP method t := engine.trees diff --git a/routes_test.go b/routes_test.go index de363a8c..e16c1376 100644 --- a/routes_test.go +++ b/routes_test.go @@ -22,7 +22,7 @@ type header struct { } func performRequest(r http.Handler, method, path string, headers ...header) *httptest.ResponseRecorder { - req, _ := http.NewRequest(method, path, nil) + req := httptest.NewRequest(method, path, nil) for _, h := range headers { req.Header.Add(h.Key, h.Value) } @@ -257,6 +257,39 @@ func TestRouteParamsByName(t *testing.T) { assert.Equal(t, "/is/super/great", wild) } +// TestContextParamsGet tests that a parameter can be parsed from the URL even with extra slashes. +func TestRouteParamsByNameWithExtraSlash(t *testing.T) { + name := "" + lastName := "" + wild := "" + router := New() + router.GET("/test/:name/:last_name/*wild", func(c *Context) { + name = c.Params.ByName("name") + lastName = c.Params.ByName("last_name") + var ok bool + wild, ok = c.Params.Get("wild") + + assert.True(t, ok) + assert.Equal(t, name, c.Param("name")) + assert.Equal(t, name, c.Param("name")) + assert.Equal(t, lastName, c.Param("last_name")) + + assert.Empty(t, c.Param("wtf")) + assert.Empty(t, c.Params.ByName("wtf")) + + wtf, ok := c.Params.Get("wtf") + assert.Empty(t, wtf) + assert.False(t, ok) + }) + + w := performRequest(router, "GET", "//test//john//smith//is//super//great") + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "john", name) + assert.Equal(t, "smith", lastName) + assert.Equal(t, "/is/super/great", wild) +} + // TestHandleStaticFile - ensure the static file handles properly func TestRouteStaticFile(t *testing.T) { // SETUP file @@ -386,15 +419,14 @@ func TestRouterNotFound(t *testing.T) { code int location string }{ - {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ - {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ - {"", http.StatusMovedPermanently, "/"}, // TSR +/ - {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case - {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case - {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ - {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ - {"/../path", http.StatusMovedPermanently, "/path"}, // CleanPath - {"/nope", http.StatusNotFound, ""}, // NotFound + {"/path/", http.StatusMovedPermanently, "/path"}, // TSR -/ + {"/dir", http.StatusMovedPermanently, "/dir/"}, // TSR +/ + {"/PATH", http.StatusMovedPermanently, "/path"}, // Fixed Case + {"/DIR/", http.StatusMovedPermanently, "/dir/"}, // Fixed Case + {"/PATH/", http.StatusMovedPermanently, "/path"}, // Fixed Case -/ + {"/DIR", http.StatusMovedPermanently, "/dir/"}, // Fixed Case +/ + {"/../path", http.StatusOK, ""}, // CleanPath + {"/nope", http.StatusNotFound, ""}, // NotFound } for _, tr := range testRoutes { w := performRequest(router, "GET", tr.route) From b75d67cd51eb53c3c3a2fc406524c940021ffbda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Tue, 7 May 2019 19:43:05 +0800 Subject: [PATCH 269/303] update vendor: ugorji/go (#1879) * update vendor: ugorji/go * fix --- go.mod | 10 +++++----- go.sum | 29 ++++++++++++--------------- vendor/vendor.json | 50 +++++++++++++++++----------------------------- 3 files changed, 36 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index 01227574..1c5e995c 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.12 require ( github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 - github.com/golang/protobuf v1.3.0 - github.com/json-iterator/go v1.1.5 - github.com/mattn/go-isatty v0.0.6 + github.com/golang/protobuf v1.3.1 + github.com/json-iterator/go v1.1.6 + github.com/mattn/go-isatty v0.0.7 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/stretchr/testify v1.3.0 - github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 - golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 + github.com/ugorji/go v1.1.4 + golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index 84cf8378..58104682 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,12 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= -github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= -github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= -github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/mattn/go-isatty v0.0.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA= -github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= @@ -17,18 +17,15 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs= -github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA= -github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU= -golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= diff --git a/vendor/vendor.json b/vendor/vendor.json index fc7bb11d..4de0bfd1 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -25,20 +25,20 @@ "versionExact": "v1.3.0" }, { - "checksumSHA1": "WqeEgS7pqqkwK8mlrAZmDgtWJMY=", + "checksumSHA1": "TB2vxux9xQbvsTHOVt4aRTuvSn4=", "path": "github.com/json-iterator/go", - "revision": "1624edc4454b8682399def8740d46db5e4362ba4", - "revisionTime": "2018-08-06T06:07:27Z", + "revision": "0ff49de124c6f76f8494e194af75bde0f1a49a29", + "revisionTime": "2019-03-06T14:29:09Z", "version": "v1.1", - "versionExact": "v1.1.5" + "versionExact": "v1.1.6" }, { - "checksumSHA1": "rrXDDvz+nQ2KRLQk6nxWaE5Zj1U=", + "checksumSHA1": "Ya+baVBU/RkXXUWD3LGFmGJiiIg=", "path": "github.com/mattn/go-isatty", - "revision": "369ecd8cea9851e459abb67eb171853e3986591e", - "revisionTime": "2019-02-25T17:38:24Z", + "revision": "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7", + "revisionTime": "2019-03-12T13:58:54Z", "version": "v0.0", - "versionExact": "v0.0.6" + "versionExact": "v0.0.7" }, { "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", @@ -81,38 +81,24 @@ "versionExact": "v1.2.2" }, { - "checksumSHA1": "fg3TzS9/QK3wZbzei3Z6O8XPLHg=", - "path": "github.com/stretchr/testify/http", - "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", - "revisionTime": "2018-12-05T02:12:43Z", - "version": "v1.3", - "versionExact": "v1.3.0" - }, - { - "checksumSHA1": "lsdl3fgOiM4Iuy7xjTQxiBtAwB0=", - "path": "github.com/stretchr/testify/mock", - "revision": "ffdc059bfe9ce6a4e144ba849dbedead332c6053", - "revisionTime": "2018-12-05T02:12:43Z", - "version": "v1.3", - "versionExact": "v1.3.0" - }, - { - "checksumSHA1": "WIhpR3EKGueRSJsYOZ6PIsfL4SI=", + "checksumSHA1": "csplo594qomjp2IZj82y7mTueOw=", "path": "github.com/ugorji/go/codec", - "revision": "e444a5086c436778cf9281a7059a3d58b9e17935", - "revisionTime": "2019-02-04T20:13:41Z" + "revision": "2adff0894ba3bc2eeb9f9aea45fefd49802e1a13", + "revisionTime": "2019-04-08T19:08:48Z", + "version": "v1.1", + "versionExact": "v1.1.4" }, { "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", "path": "golang.org/x/net/context", - "revision": "49bb7cea24b1df9410e1712aa6433dae904ff66a", - "revisionTime": "2018-10-11T05:27:23Z" + "revision": "f4e77d36d62c17c2336347bb2670ddbd02d092b7", + "revisionTime": "2019-05-02T22:26:14Z" }, { - "checksumSHA1": "SiJNkx+YGtq3Gtr6Ldu6OW83O+U=", + "checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=", "path": "golang.org/x/sys/unix", - "revision": "fa43e7bc11baaae89f3f902b2b4d832b68234844", - "revisionTime": "2018-10-11T14:35:51Z" + "revision": "a43fa875dd822b81eb6d2ad538bc1f4caba169bd", + "revisionTime": "2019-05-02T15:41:39Z" }, { "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", From 5a7e3095b29adc7a9caf89fe570badff28997be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 8 May 2019 11:10:34 +0800 Subject: [PATCH 270/303] Update README.md about go version (#1885) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e817a78..e980edbc 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ $ go run main.go ## Prerequisite -Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. +Now Gin requires Go 1.8 or later and Go 1.10 will be required next major version. ## Quick start From 04eecb1283dbd84c3babae7b3923ac71b18a56f9 Mon Sep 17 00:00:00 2001 From: Uwe Dauernheim Date: Fri, 10 May 2019 08:03:25 +0200 Subject: [PATCH 271/303] Use DefaultWriter and DefaultErrorWriter for debug messages (#1891) Aligns behaviour according to documentation. --- debug.go | 7 ++++--- debug_test.go | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/debug.go b/debug.go index 6d40a5da..19e380fb 100644 --- a/debug.go +++ b/debug.go @@ -8,7 +8,6 @@ import ( "bytes" "fmt" "html/template" - "os" "runtime" "strconv" "strings" @@ -54,7 +53,7 @@ func debugPrint(format string, values ...interface{}) { if !strings.HasSuffix(format, "\n") { format += "\n" } - fmt.Fprintf(os.Stderr, "[GIN-debug] "+format, values...) + fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...) } } @@ -98,6 +97,8 @@ at initialization. ie. before any route is registered or the router is listening func debugPrintError(err error) { if err != nil { - debugPrint("[ERROR] %v\n", err) + if IsDebugging() { + fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err) + } } } diff --git a/debug_test.go b/debug_test.go index 86a67773..9ace2989 100644 --- a/debug_test.go +++ b/debug_test.go @@ -111,15 +111,15 @@ func captureOutput(t *testing.T, f func()) string { if err != nil { panic(err) } - stdout := os.Stdout - stderr := os.Stderr + defaultWriter := DefaultWriter + defaultErrorWriter := DefaultErrorWriter defer func() { - os.Stdout = stdout - os.Stderr = stderr + DefaultWriter = defaultWriter + DefaultErrorWriter = defaultErrorWriter log.SetOutput(os.Stderr) }() - os.Stdout = writer - os.Stderr = writer + DefaultWriter = writer + DefaultErrorWriter = writer log.SetOutput(writer) out := make(chan string) wg := new(sync.WaitGroup) From 965d74cebb31c1e5e0c09ec256c32d0c5db9072c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Sun, 12 May 2019 18:47:27 +0800 Subject: [PATCH 272/303] add dev version (#1886) * add dev version * Update version.go * Update version.go --- README.md | 4 ---- version.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index e980edbc..10fb1d45 100644 --- a/README.md +++ b/README.md @@ -119,10 +119,6 @@ $ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go $ go run main.go ``` -## Prerequisite - -Now Gin requires Go 1.8 or later and Go 1.10 will be required next major version. - ## Quick start ```sh diff --git a/version.go b/version.go index 07e7859f..028caebe 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,4 @@ package gin // Version is the current gin framework's version. -const Version = "v1.4.0" +const Version = "v1.4.0-dev" From 8ee9d959a0bcc132fae25ce61881c7effbe5c2f5 Mon Sep 17 00:00:00 2001 From: guonaihong Date: Mon, 13 May 2019 10:17:31 +0800 Subject: [PATCH 273/303] Now you can parse the inline lowercase start structure (#1893) * Now you can parse the inline lowercase start structure package main import ( "encoding/json" "fmt" "github.com/gin-gonic/gin" ) type appkey struct { Appkey string `json:"appkey" form:"appkey"` } type Query struct { Page int `json:"page" form:"page"` Size int `json:"size" form:"size"` appkey } func main() { router := gin.Default() router.POST("/login", func(c *gin.Context) { var q2 Query if c.ShouldBindQuery(&q2) == nil { c.JSON(200, &q2) } }) router.Run(":8088") } http client: old: curl -X POST "127.0.0.1:8088/login?appkey=china&page=1&size=10" {"page":1,"size":10,"appkey":""} now: curl -X POST "127.0.0.1:8088/login?appkey=china&page=1&size=10" {"page":1,"size":10,"appkey":"china"} * Modify judgment conditions --- binding/binding_test.go | 39 +++++++++++++++++++++++++++++++++++++++ binding/form_mapping.go | 17 ++++++++++------- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/binding/binding_test.go b/binding/binding_test.go index 73bb7700..6710e42b 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -24,6 +24,16 @@ import ( "github.com/ugorji/go/codec" ) +type appkey struct { + Appkey string `json:"appkey" form:"appkey"` +} + +type QueryTest struct { + Page int `json:"page" form:"page"` + Size int `json:"size" form:"size"` + appkey +} + type FooStruct struct { Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"` } @@ -189,6 +199,18 @@ func TestBindingForm2(t *testing.T) { "", "") } +func TestBindingFormEmbeddedStruct(t *testing.T) { + testFormBindingEmbeddedStruct(t, "POST", + "/", "/", + "page=1&size=2&appkey=test-appkey", "bar2=foo") +} + +func TestBindingFormEmbeddedStruct2(t *testing.T) { + testFormBindingEmbeddedStruct(t, "GET", + "/?page=1&size=2&appkey=test-appkey", "/?bar2=foo", + "", "") +} + func TestBindingFormDefaultValue(t *testing.T) { testFormBindingDefaultValue(t, "POST", "/", "/", @@ -688,6 +710,23 @@ func TestUriInnerBinding(t *testing.T) { assert.Equal(t, tag.S.Age, expectedAge) } +func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, "form", b.Name()) + + obj := QueryTest{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.NoError(t, err) + assert.Equal(t, 1, obj.Page) + assert.Equal(t, 2, obj.Size) + assert.Equal(t, "test-appkey", obj.Appkey) + +} + func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index aaacf6c5..32c5b668 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -70,12 +70,14 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag return isSetted, nil } - ok, err := tryToSetValue(value, field, setter, tag) - if err != nil { - return false, err - } - if ok { - return true, nil + if vKind != reflect.Struct || !field.Anonymous { + ok, err := tryToSetValue(value, field, setter, tag) + if err != nil { + return false, err + } + if ok { + return true, nil + } } if vKind == reflect.Struct { @@ -83,7 +85,8 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag var isSetted bool for i := 0; i < value.NumField(); i++ { - if !value.Field(i).CanSet() { + sf := tValue.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { // unexported continue } ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) From b1d607a8991147c4f3c905cd4e766eb35c83fdfa Mon Sep 17 00:00:00 2001 From: Kirill Motkov Date: Tue, 21 May 2019 18:08:52 +0300 Subject: [PATCH 274/303] Some code improvements (#1909) * strings.ToLower comparison changed to strings.EqualFold. * Rewrite switch statement with only one case as if. --- binding/form_mapping.go | 4 +--- context.go | 2 +- tree.go | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index 32c5b668..ebf3b199 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -126,9 +126,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter for len(opts) > 0 { opt, opts = head(opts, ",") - k, v := head(opt, "=") - switch k { - case "default": + if k, v := head(opt, "="); k == "default" { setOpt.isDefaultExists = true setOpt.defaultValue = v } diff --git a/context.go b/context.go index af747a1e..a3a7bc47 100644 --- a/context.go +++ b/context.go @@ -671,7 +671,7 @@ func (c *Context) ContentType() string { // handshake is being initiated by the client. func (c *Context) IsWebsocket() bool { if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") && - strings.ToLower(c.requestHeader("Upgrade")) == "websocket" { + strings.EqualFold(c.requestHeader("Upgrade"), "websocket") { return true } return false diff --git a/tree.go b/tree.go index ada62ceb..07d6b4be 100644 --- a/tree.go +++ b/tree.go @@ -514,7 +514,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory // Outer loop for walking the tree - for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) { + for len(path) >= len(n.path) && strings.EqualFold(path[:len(n.path)], n.path) { path = path[len(n.path):] ciPath = append(ciPath, n.path...) @@ -618,7 +618,7 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPa return ciPath, true } if len(path)+1 == len(n.path) && n.path[len(path)] == '/' && - strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) && + strings.EqualFold(path, n.path[:len(path)]) && n.handlers != nil { return append(ciPath, n.path...), true } From 0cbf290302ae06b647989effaf5e5a23028670d7 Mon Sep 17 00:00:00 2001 From: itcloudy <272685110@qq.com> Date: Wed, 22 May 2019 07:48:50 +0800 Subject: [PATCH 275/303] use encode replace json marshal increase json encoder speed (#1546) --- context_test.go | 10 +++++----- logger_test.go | 6 +++--- middleware_test.go | 2 +- render/json.go | 7 ++----- render/render_test.go | 2 +- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/context_test.go b/context_test.go index 490e4490..e8dcd3dc 100644 --- a/context_test.go +++ b/context_test.go @@ -660,7 +660,7 @@ func TestContextRenderJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar", "html": ""}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -688,7 +688,7 @@ func TestContextRenderJSONPWithoutCallback(t *testing.T) { c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -714,7 +714,7 @@ func TestContextRenderAPIJSON(t *testing.T) { c.JSON(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String()) assert.Equal(t, "application/vnd.api+json", w.Header().Get("Content-Type")) } @@ -1117,7 +1117,7 @@ func TestContextNegotiationWithJSON(t *testing.T) { }) assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } @@ -1281,7 +1281,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) { _, err := buf.ReadFrom(w.Body) assert.NoError(t, err) jsonStringBody := buf.String() - assert.Equal(t, fmt.Sprint(`{"foo":"fooValue","bar":"barValue"}`), jsonStringBody) + assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}\n"), jsonStringBody) } func TestContextError(t *testing.T) { diff --git a/logger_test.go b/logger_test.go index 56bb3a00..9177e1d9 100644 --- a/logger_test.go +++ b/logger_test.go @@ -369,15 +369,15 @@ func TestErrorLogger(t *testing.T) { w := performRequest(router, "GET", "/error") assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, "{\"error\":\"this is an error\"}", w.Body.String()) + assert.Equal(t, "{\"error\":\"this is an error\"}\n", w.Body.String()) w = performRequest(router, "GET", "/abort") assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Equal(t, "{\"error\":\"no authorized\"}", w.Body.String()) + assert.Equal(t, "{\"error\":\"no authorized\"}\n", w.Body.String()) w = performRequest(router, "GET", "/print") assert.Equal(t, http.StatusInternalServerError, w.Code) - assert.Equal(t, "hola!{\"error\":\"this is an error\"}", w.Body.String()) + assert.Equal(t, "hola!{\"error\":\"this is an error\"}\n", w.Body.String()) } func TestLoggerWithWriterSkippingPaths(t *testing.T) { diff --git a/middleware_test.go b/middleware_test.go index fca1c530..2ae9e889 100644 --- a/middleware_test.go +++ b/middleware_test.go @@ -246,5 +246,5 @@ func TestMiddlewareWrite(t *testing.T) { w := performRequest(router, "GET", "/") assert.Equal(t, http.StatusBadRequest, w.Code) - assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}{\"foo\":\"bar\"}event:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) + assert.Equal(t, strings.Replace("hola\nbar{\"foo\":\"bar\"}\n{\"foo\":\"bar\"}\nevent:test\ndata:message\n\n", " ", "", -1), strings.Replace(w.Body.String(), " ", "", -1)) } diff --git a/render/json.go b/render/json.go index 18f27fa9..2b07cba0 100644 --- a/render/json.go +++ b/render/json.go @@ -68,11 +68,8 @@ func (r JSON) WriteContentType(w http.ResponseWriter) { // WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) - jsonBytes, err := json.Marshal(obj) - if err != nil { - return err - } - _, err = w.Write(jsonBytes) + encoder := json.NewEncoder(w) + err := encoder.Encode(&obj) return err } diff --git a/render/render_test.go b/render/render_test.go index 3aa5dbcc..9d7eaeef 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -62,7 +62,7 @@ func TestRenderJSON(t *testing.T) { err := (JSON{data}).Render(w) assert.NoError(t, err) - assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) + assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) } From 78a8b5c9d58ed7a81f3e55aaf7e4b1825f2cfecd Mon Sep 17 00:00:00 2001 From: ZYunH Date: Thu, 23 May 2019 11:37:34 +0800 Subject: [PATCH 276/303] Fix typo (#1913) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 10fb1d45..2737e9ad 100644 --- a/README.md +++ b/README.md @@ -1694,7 +1694,7 @@ func main() { quit := make(chan os.Signal) // kill (no param) default send syscall.SIGTERM // kill -2 is syscall.SIGINT - // kill -9 is syscall.SIGKILL but can"t be catch, so don't need add it + // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutdown Server ...") From 35e33d3638f9b5a1246dd9c72a99740f5ad4b43b Mon Sep 17 00:00:00 2001 From: Roman Zaynetdinov <627197+zaynetro@users.noreply.github.com> Date: Sun, 26 May 2019 03:20:21 +0300 Subject: [PATCH 277/303] Hold matched route full path in the Context (#1826) * Return nodeValue from getValue method * Hold route full path in the Context * Add small example --- README.md | 5 ++++ context.go | 11 ++++++++ gin.go | 14 +++++----- routes_test.go | 35 ++++++++++++++++++++++++ tree.go | 72 +++++++++++++++++++++++++++++++------------------- tree_test.go | 26 +++++++++--------- 6 files changed, 117 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 2737e9ad..092e91ff 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,11 @@ func main() { c.String(http.StatusOK, message) }) + // For each matched request Context will hold the route definition + router.POST("/user/:name/*action", func(c *gin.Context) { + c.FullPath() == "/user/:name/*action" // true + }) + router.Run(":8080") } ``` diff --git a/context.go b/context.go index a3a7bc47..425b627f 100644 --- a/context.go +++ b/context.go @@ -48,6 +48,7 @@ type Context struct { Params Params handlers HandlersChain index int8 + fullPath string engine *Engine @@ -70,6 +71,7 @@ func (c *Context) reset() { c.Params = c.Params[0:0] c.handlers = nil c.index = -1 + c.fullPath = "" c.Keys = nil c.Errors = c.Errors[0:0] c.Accepted = nil @@ -111,6 +113,15 @@ func (c *Context) Handler() HandlerFunc { return c.handlers.Last() } +// FullPath returns a matched route full path. For not found routes +// returns an empty string. +// router.GET("/user/:id", func(c *gin.Context) { +// c.FullPath() == "/user/:id" // true +// }) +func (c *Context) FullPath() string { + return c.fullPath +} + /************************************/ /*********** FLOW CONTROL ***********/ /************************************/ diff --git a/gin.go b/gin.go index 4dbe9836..220f0401 100644 --- a/gin.go +++ b/gin.go @@ -252,6 +252,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) { root := engine.trees.get(method) if root == nil { root = new(node) + root.fullPath = "/" engine.trees = append(engine.trees, methodTree{method: method, root: root}) } root.addRoute(path, handlers) @@ -382,16 +383,17 @@ func (engine *Engine) handleHTTPRequest(c *Context) { } root := t[i].root // Find route in tree - handlers, params, tsr := root.getValue(rPath, c.Params, unescape) - if handlers != nil { - c.handlers = handlers - c.Params = params + value := root.getValue(rPath, c.Params, unescape) + if value.handlers != nil { + c.handlers = value.handlers + c.Params = value.params + c.fullPath = value.fullPath c.Next() c.writermem.WriteHeaderNow() return } if httpMethod != "CONNECT" && rPath != "/" { - if tsr && engine.RedirectTrailingSlash { + if value.tsr && engine.RedirectTrailingSlash { redirectTrailingSlash(c) return } @@ -407,7 +409,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { if tree.method == httpMethod { continue } - if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil { + if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil { c.handlers = engine.allNoMethod serveError(c, http.StatusMethodNotAllowed, default405Body) return diff --git a/routes_test.go b/routes_test.go index e16c1376..457c923e 100644 --- a/routes_test.go +++ b/routes_test.go @@ -554,3 +554,38 @@ func TestRouteServeErrorWithWriteHeader(t *testing.T) { assert.Equal(t, 421, w.Code) assert.Equal(t, 0, w.Body.Len()) } + +func TestRouteContextHoldsFullPath(t *testing.T) { + router := New() + + // Test routes + routes := []string{ + "/", + "/simple", + "/project/:name", + "/project/:name/build/*params", + } + + for _, route := range routes { + actualRoute := route + router.GET(route, func(c *Context) { + // For each defined route context should contain its full path + assert.Equal(t, actualRoute, c.FullPath()) + c.AbortWithStatus(http.StatusOK) + }) + } + + for _, route := range routes { + w := performRequest(router, "GET", route) + assert.Equal(t, http.StatusOK, w.Code) + } + + // Test not found + router.Use(func(c *Context) { + // For not found routes full path is empty + assert.Equal(t, "", c.FullPath()) + }) + + w := performRequest(router, "GET", "/not-found") + assert.Equal(t, http.StatusNotFound, w.Code) +} diff --git a/tree.go b/tree.go index 07d6b4be..9a789f2f 100644 --- a/tree.go +++ b/tree.go @@ -94,6 +94,7 @@ type node struct { nType nodeType maxParams uint8 wildChild bool + fullPath string } // increments priority of the given child and reorders if necessary. @@ -154,6 +155,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { children: n.children, handlers: n.handlers, priority: n.priority - 1, + fullPath: fullPath, } // Update maxParams (max of all children) @@ -229,6 +231,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { n.indices += string([]byte{c}) child := &node{ maxParams: numParams, + fullPath: fullPath, } n.children = append(n.children, child) n.incrementChildPrio(len(n.indices) - 1) @@ -296,6 +299,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle child := &node{ nType: param, maxParams: numParams, + fullPath: fullPath, } n.children = []*node{child} n.wildChild = true @@ -312,6 +316,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle child := &node{ maxParams: numParams, priority: 1, + fullPath: fullPath, } n.children = []*node{child} n = child @@ -339,6 +344,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle wildChild: true, nType: catchAll, maxParams: 1, + fullPath: fullPath, } n.children = []*node{child} n.indices = string(path[i]) @@ -352,6 +358,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle maxParams: 1, handlers: handlers, priority: 1, + fullPath: fullPath, } n.children = []*node{child} @@ -364,13 +371,21 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle n.handlers = handlers } +// nodeValue holds return values of (*Node).getValue method +type nodeValue struct { + handlers HandlersChain + params Params + tsr bool + fullPath string +} + // getValue returns the handle registered with the given path (key). The values of // wildcards are saved to a map. // If no handle can be found, a TSR (trailing slash redirect) recommendation is // made if a handle exists with an extra (without the) trailing slash for the // given path. -func (n *node) getValue(path string, po Params, unescape bool) (handlers HandlersChain, p Params, tsr bool) { - p = po +func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) { + value.params = po walk: // Outer loop for walking the tree for { if len(path) > len(n.path) { @@ -391,7 +406,7 @@ walk: // Outer loop for walking the tree // Nothing found. // We can recommend to redirect to the same URL without a // trailing slash if a leaf exists for that path. - tsr = path == "/" && n.handlers != nil + value.tsr = path == "/" && n.handlers != nil return } @@ -406,20 +421,20 @@ walk: // Outer loop for walking the tree } // save param value - if cap(p) < int(n.maxParams) { - p = make(Params, 0, n.maxParams) + if cap(value.params) < int(n.maxParams) { + value.params = make(Params, 0, n.maxParams) } - i := len(p) - p = p[:i+1] // expand slice within preallocated capacity - p[i].Key = n.path[1:] + i := len(value.params) + value.params = value.params[:i+1] // expand slice within preallocated capacity + value.params[i].Key = n.path[1:] val := path[:end] if unescape { var err error - if p[i].Value, err = url.QueryUnescape(val); err != nil { - p[i].Value = val // fallback, in case of error + if value.params[i].Value, err = url.QueryUnescape(val); err != nil { + value.params[i].Value = val // fallback, in case of error } } else { - p[i].Value = val + value.params[i].Value = val } // we need to go deeper! @@ -431,40 +446,42 @@ walk: // Outer loop for walking the tree } // ... but we can't - tsr = len(path) == end+1 + value.tsr = len(path) == end+1 return } - if handlers = n.handlers; handlers != nil { + if value.handlers = n.handlers; value.handlers != nil { + value.fullPath = n.fullPath return } if len(n.children) == 1 { // No handle found. Check if a handle for this path + a // trailing slash exists for TSR recommendation n = n.children[0] - tsr = n.path == "/" && n.handlers != nil + value.tsr = n.path == "/" && n.handlers != nil } return case catchAll: // save param value - if cap(p) < int(n.maxParams) { - p = make(Params, 0, n.maxParams) + if cap(value.params) < int(n.maxParams) { + value.params = make(Params, 0, n.maxParams) } - i := len(p) - p = p[:i+1] // expand slice within preallocated capacity - p[i].Key = n.path[2:] + i := len(value.params) + value.params = value.params[:i+1] // expand slice within preallocated capacity + value.params[i].Key = n.path[2:] if unescape { var err error - if p[i].Value, err = url.QueryUnescape(path); err != nil { - p[i].Value = path // fallback, in case of error + if value.params[i].Value, err = url.QueryUnescape(path); err != nil { + value.params[i].Value = path // fallback, in case of error } } else { - p[i].Value = path + value.params[i].Value = path } - handlers = n.handlers + value.handlers = n.handlers + value.fullPath = n.fullPath return default: @@ -474,12 +491,13 @@ walk: // Outer loop for walking the tree } else if path == n.path { // We should have reached the node containing the handle. // Check if this node has a handle registered. - if handlers = n.handlers; handlers != nil { + if value.handlers = n.handlers; value.handlers != nil { + value.fullPath = n.fullPath return } if path == "/" && n.wildChild && n.nType != root { - tsr = true + value.tsr = true return } @@ -488,7 +506,7 @@ walk: // Outer loop for walking the tree for i := 0; i < len(n.indices); i++ { if n.indices[i] == '/' { n = n.children[i] - tsr = (len(n.path) == 1 && n.handlers != nil) || + value.tsr = (len(n.path) == 1 && n.handlers != nil) || (n.nType == catchAll && n.children[0].handlers != nil) return } @@ -499,7 +517,7 @@ walk: // Outer loop for walking the tree // Nothing found. We can recommend to redirect to the same URL with an // extra trailing slash if a leaf exists for that path - tsr = (path == "/") || + value.tsr = (path == "/") || (len(n.path) == len(path)+1 && n.path[len(path)] == '/' && path == n.path[:len(n.path)-1] && n.handlers != nil) return diff --git a/tree_test.go b/tree_test.go index dbb0352b..e6e28865 100644 --- a/tree_test.go +++ b/tree_test.go @@ -35,22 +35,22 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes .. } for _, request := range requests { - handler, ps, _ := tree.getValue(request.path, nil, unescape) + value := tree.getValue(request.path, nil, unescape) - if handler == nil { + if value.handlers == nil { if !request.nilHandler { t.Errorf("handle mismatch for route '%s': Expected non-nil handle", request.path) } } else if request.nilHandler { t.Errorf("handle mismatch for route '%s': Expected nil handle", request.path) } else { - handler[0](nil) + value.handlers[0](nil) if fakeHandlerValue != request.route { t.Errorf("handle mismatch for route '%s': Wrong handle (%s != %s)", request.path, fakeHandlerValue, request.route) } } - if !reflect.DeepEqual(ps, request.ps) { + if !reflect.DeepEqual(value.params, request.ps) { t.Errorf("Params mismatch for route '%s'", request.path) } } @@ -454,10 +454,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/doc/", } for _, route := range tsrRoutes { - handler, _, tsr := tree.getValue(route, nil, false) - if handler != nil { + value := tree.getValue(route, nil, false) + if value.handlers != nil { t.Fatalf("non-nil handler for TSR route '%s", route) - } else if !tsr { + } else if !value.tsr { t.Errorf("expected TSR recommendation for route '%s'", route) } } @@ -471,10 +471,10 @@ func TestTreeTrailingSlashRedirect(t *testing.T) { "/api/world/abc", } for _, route := range noTsrRoutes { - handler, _, tsr := tree.getValue(route, nil, false) - if handler != nil { + value := tree.getValue(route, nil, false) + if value.handlers != nil { t.Fatalf("non-nil handler for No-TSR route '%s", route) - } else if tsr { + } else if value.tsr { t.Errorf("expected no TSR recommendation for route '%s'", route) } } @@ -490,10 +490,10 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) { t.Fatalf("panic inserting test route: %v", recv) } - handler, _, tsr := tree.getValue("/", nil, false) - if handler != nil { + value := tree.getValue("/", nil, false) + if value.handlers != nil { t.Fatalf("non-nil handler") - } else if tsr { + } else if value.tsr { t.Errorf("expected no TSR recommendation") } } From 6e320c97e83a61df3daa0f28695a736bece3104b Mon Sep 17 00:00:00 2001 From: Samuel Abreu Date: Mon, 27 May 2019 03:04:30 -0300 Subject: [PATCH 278/303] Fix context.Params race condition on Copy() (#1841) * Fix context.Params race condition on Copy() Using context.Param(key) on a context.Copy inside a goroutine may lead to incorrect value on a high load, where another request overwrite a Param * Using waitgroup to wait asynchronous test case --- context.go | 3 +++ context_test.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/context.go b/context.go index 425b627f..3f1f8ca0 100644 --- a/context.go +++ b/context.go @@ -89,6 +89,9 @@ func (c *Context) Copy() *Context { for k, v := range c.Keys { cp.Keys[k] = v } + paramCopy := make([]Param, len(cp.Params)) + copy(paramCopy, cp.Params) + cp.Params = paramCopy return &cp } diff --git a/context_test.go b/context_test.go index e8dcd3dc..89cfb446 100644 --- a/context_test.go +++ b/context_test.go @@ -13,8 +13,10 @@ import ( "mime/multipart" "net/http" "net/http/httptest" + "os" "reflect" "strings" + "sync" "testing" "time" @@ -1821,3 +1823,24 @@ func TestContextResetInHandler(t *testing.T) { c.Next() }) } + +func TestRaceParamsContextCopy(t *testing.T) { + DefaultWriter = os.Stdout + router := Default() + nameGroup := router.Group("/:name") + var wg sync.WaitGroup + wg.Add(2) + { + nameGroup.GET("/api", func(c *Context) { + go func(c *Context, param string) { + defer wg.Done() + // First assert must be executed after the second request + time.Sleep(50 * time.Millisecond) + assert.Equal(t, c.Param("name"), param) + }(c.Copy(), c.Param("name")) + }) + } + performRequest(router, "GET", "/name1/api") + performRequest(router, "GET", "/name2/api") + wg.Wait() +} From 233a3e493d2adac62141371c983b55e71d805ca3 Mon Sep 17 00:00:00 2001 From: ijaa Date: Wed, 29 May 2019 11:25:02 +0800 Subject: [PATCH 279/303] add context param query cache (#1450) --- context.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index 3f1f8ca0..64810d54 100644 --- a/context.go +++ b/context.go @@ -60,6 +60,9 @@ type Context struct { // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string + + // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() + queryCache url.Values } /************************************/ @@ -75,6 +78,7 @@ func (c *Context) reset() { c.Keys = nil c.Errors = c.Errors[0:0] c.Accepted = nil + c.queryCache = nil } // Copy returns a copy of the current context that can be safely used outside the request's scope. @@ -385,7 +389,13 @@ func (c *Context) QueryArray(key string) []string { // GetQueryArray returns a slice of strings for a given query key, plus // a boolean value whether at least one value exists for the given key. func (c *Context) GetQueryArray(key string) ([]string, bool) { - if values, ok := c.Request.URL.Query()[key]; ok && len(values) > 0 { + + if c.queryCache == nil { + c.queryCache = make(url.Values) + c.queryCache, _ = url.ParseQuery(c.Request.URL.RawQuery) + } + + if values, ok := c.queryCache[key]; ok && len(values) > 0 { return values, true } return []string{}, false From 4b6df417e4bda80a698a64aa085779a7ad1269c0 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 29 May 2019 14:54:55 +0800 Subject: [PATCH 280/303] chore: improve GetQueryMap performance. (#1918) Signed-off-by: Bo-Yi Wu --- context.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 64810d54..71337acd 100644 --- a/context.go +++ b/context.go @@ -386,15 +386,17 @@ func (c *Context) QueryArray(key string) []string { return values } -// GetQueryArray returns a slice of strings for a given query key, plus -// a boolean value whether at least one value exists for the given key. -func (c *Context) GetQueryArray(key string) ([]string, bool) { - +func (c *Context) getQueryCache() { if c.queryCache == nil { c.queryCache = make(url.Values) c.queryCache, _ = url.ParseQuery(c.Request.URL.RawQuery) } +} +// GetQueryArray returns a slice of strings for a given query key, plus +// a boolean value whether at least one value exists for the given key. +func (c *Context) GetQueryArray(key string) ([]string, bool) { + c.getQueryCache() if values, ok := c.queryCache[key]; ok && len(values) > 0 { return values, true } @@ -410,7 +412,8 @@ func (c *Context) QueryMap(key string) map[string]string { // GetQueryMap returns a map for a given query key, plus a boolean value // whether at least one value exists for the given key. func (c *Context) GetQueryMap(key string) (map[string]string, bool) { - return c.get(c.Request.URL.Query(), key) + c.getQueryCache() + return c.get(c.queryCache, key) } // PostForm returns the specified key from a POST urlencoded form or multipart form From 08b52e5394099db4c2399357e060619c1545083e Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Sun, 2 Jun 2019 17:24:41 +0800 Subject: [PATCH 281/303] feat: improve get post data. (#1920) Signed-off-by: Bo-Yi Wu --- context.go | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/context.go b/context.go index 71337acd..ffb9a2de 100644 --- a/context.go +++ b/context.go @@ -61,8 +61,12 @@ type Context struct { // Accepted defines a list of manually accepted formats for content negotiation. Accepted []string - // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() + // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query() queryCache url.Values + + // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH, + // or PUT body parameters. + formCache url.Values } /************************************/ @@ -454,16 +458,24 @@ func (c *Context) PostFormArray(key string) []string { return values } +func (c *Context) getFormCache() { + if c.formCache == nil { + c.formCache = make(url.Values) + req := c.Request + if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { + if err != http.ErrNotMultipart { + debugPrint("error on parse multipart form array: %v", err) + } + } + c.formCache = req.PostForm + } +} + // GetPostFormArray returns a slice of strings for a given form key, plus // a boolean value whether at least one value exists for the given key. func (c *Context) GetPostFormArray(key string) ([]string, bool) { - req := c.Request - if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { - if err != http.ErrNotMultipart { - debugPrint("error on parse multipart form array: %v", err) - } - } - if values := req.PostForm[key]; len(values) > 0 { + c.getFormCache() + if values := c.formCache[key]; len(values) > 0 { return values, true } return []string{}, false From bfecd88fc4c22acdc93585ecc44b3a10d9702e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 3 Jun 2019 22:42:25 +0800 Subject: [PATCH 282/303] use sse v0.1.0 (#1923) --- go.mod | 2 +- go.sum | 4 ++-- vendor/vendor.json | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 1c5e995c..7680d4f7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/gin-gonic/gin go 1.12 require ( - github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 + github.com/gin-contrib/sse v0.1.0 github.com/golang/protobuf v1.3.1 github.com/json-iterator/go v1.1.6 github.com/mattn/go-isatty v0.0.7 diff --git a/go.sum b/go.sum index 58104682..8610eae2 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= diff --git a/vendor/vendor.json b/vendor/vendor.json index 4de0bfd1..3e9d13b7 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -11,10 +11,12 @@ "versionExact": "v1.1.1" }, { - "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", + "checksumSHA1": "qlEzrgKgIkh7y0ePm9BNo1cNdXo=", "path": "github.com/gin-contrib/sse", - "revision": "5545eab6dad3bbbd6c5ae9186383c2a9d23c0dae", - "revisionTime": "2019-03-01T06:25:29Z" + "revision": "54d8467d122d380a14768b6b4e5cd7ca4755938f", + "revisionTime": "2019-06-02T15:02:53Z", + "version": "v0.1", + "versionExact": "v0.1.0" }, { "checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=", @@ -118,4 +120,4 @@ } ], "rootPath": "github.com/gin-gonic/gin" -} \ No newline at end of file +} From 73c4633943d596bdbeaa7d02cebdd4bd0c4f4630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Mon, 3 Jun 2019 22:52:33 +0800 Subject: [PATCH 283/303] use context instead of x/net/context (#1922) --- context_test.go | 2 +- vendor/vendor.json | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/context_test.go b/context_test.go index 89cfb446..b6ecb280 100644 --- a/context_test.go +++ b/context_test.go @@ -6,6 +6,7 @@ package gin import ( "bytes" + "context" "errors" "fmt" "html/template" @@ -24,7 +25,6 @@ import ( "github.com/gin-gonic/gin/binding" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" - "golang.org/x/net/context" testdata "github.com/gin-gonic/gin/testdata/protoexample" ) diff --git a/vendor/vendor.json b/vendor/vendor.json index 3e9d13b7..a225eb57 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -90,12 +90,6 @@ "version": "v1.1", "versionExact": "v1.1.4" }, - { - "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", - "path": "golang.org/x/net/context", - "revision": "f4e77d36d62c17c2336347bb2670ddbd02d092b7", - "revisionTime": "2019-05-02T22:26:14Z" - }, { "checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=", "path": "golang.org/x/sys/unix", From 75b9d2bed75a2d96198738ac3974211924dbde6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E6=AC=A7?= Date: Wed, 12 Jun 2019 21:07:15 +0800 Subject: [PATCH 284/303] Attempt to fix PostForm cache bug (#1931) --- context.go | 1 + 1 file changed, 1 insertion(+) diff --git a/context.go b/context.go index ffb9a2de..77cdc182 100644 --- a/context.go +++ b/context.go @@ -83,6 +83,7 @@ func (c *Context) reset() { c.Errors = c.Errors[0:0] c.Accepted = nil c.queryCache = nil + c.formCache = nil } // Copy returns a copy of the current context that can be safely used outside the request's scope. From 09a3650c97ca7ef3a542d428a9fb2c8da8c18002 Mon Sep 17 00:00:00 2001 From: Dmitry Kutakov Date: Tue, 18 Jun 2019 14:49:10 +0300 Subject: [PATCH 285/303] binding: add support of multipart multi files (#1878) (#1949) * binding: add support of multipart multi files (#1878) * update readme: add multipart file binding --- README.md | 38 ++++--- binding/form.go | 26 ----- binding/multipart_form_mapping.go | 66 ++++++++++++ binding/multipart_form_mapping_test.go | 138 +++++++++++++++++++++++++ 4 files changed, 225 insertions(+), 43 deletions(-) create mode 100644 binding/multipart_form_mapping.go create mode 100644 binding/multipart_form_mapping_test.go diff --git a/README.md b/README.md index 092e91ff..49b044aa 100644 --- a/README.md +++ b/README.md @@ -959,32 +959,36 @@ result: ### Multipart/Urlencoded binding ```go -package main +type ProfileForm struct { + Name string `form:"name" binding:"required"` + Avatar *multipart.FileHeader `form:"avatar" binding:"required"` -import ( - "github.com/gin-gonic/gin" -) - -type LoginForm struct { - User string `form:"user" binding:"required"` - Password string `form:"password" binding:"required"` + // or for multiple files + // Avatars []*multipart.FileHeader `form:"avatar" binding:"required"` } func main() { router := gin.Default() - router.POST("/login", func(c *gin.Context) { + router.POST("/profile", func(c *gin.Context) { // you can bind multipart form with explicit binding declaration: // c.ShouldBindWith(&form, binding.Form) // or you can simply use autobinding with ShouldBind method: - var form LoginForm + var form ProfileForm // in this case proper binding will be automatically selected - if c.ShouldBind(&form) == nil { - if form.User == "user" && form.Password == "password" { - c.JSON(200, gin.H{"status": "you are logged in"}) - } else { - c.JSON(401, gin.H{"status": "unauthorized"}) - } + if err := c.ShouldBind(&form); err != nil { + c.String(http.StatusBadRequest, "bad request") + return } + + err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename) + if err != nil { + c.String(http.StatusInternalServerError, "unknown error") + return + } + + // db.Save(&form) + + c.String(http.StatusOK, "ok") }) router.Run(":8080") } @@ -992,7 +996,7 @@ func main() { Test it with: ```sh -$ curl -v --form user=user --form password=password http://localhost:8080/login +$ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile ``` ### XML, JSON, YAML and ProtoBuf rendering diff --git a/binding/form.go b/binding/form.go index 0b28aa8a..9e9fc3de 100644 --- a/binding/form.go +++ b/binding/form.go @@ -5,9 +5,7 @@ package binding import ( - "mime/multipart" "net/http" - "reflect" ) const defaultMemory = 32 * 1024 * 1024 @@ -63,27 +61,3 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { return validate(obj) } - -type multipartRequest http.Request - -var _ setter = (*multipartRequest)(nil) - -var ( - multipartFileHeaderStructType = reflect.TypeOf(multipart.FileHeader{}) -) - -// TrySet tries to set a value by the multipart request with the binding a form file -func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { - if value.Type() == multipartFileHeaderStructType { - _, file, err := (*http.Request)(r).FormFile(key) - if err != nil { - return false, err - } - if file != nil { - value.Set(reflect.ValueOf(*file)) - return true, nil - } - } - - return setByForm(value, field, r.MultipartForm.Value, key, opt) -} diff --git a/binding/multipart_form_mapping.go b/binding/multipart_form_mapping.go new file mode 100644 index 00000000..f85a1aa6 --- /dev/null +++ b/binding/multipart_form_mapping.go @@ -0,0 +1,66 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "errors" + "mime/multipart" + "net/http" + "reflect" +) + +type multipartRequest http.Request + +var _ setter = (*multipartRequest)(nil) + +// TrySet tries to set a value by the multipart request with the binding a form file +func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) { + if files := r.MultipartForm.File[key]; len(files) != 0 { + return setByMultipartFormFile(value, field, files) + } + + return setByForm(value, field, r.MultipartForm.Value, key, opt) +} + +func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { + switch value.Kind() { + case reflect.Ptr: + switch value.Interface().(type) { + case *multipart.FileHeader: + value.Set(reflect.ValueOf(files[0])) + return true, nil + } + case reflect.Struct: + switch value.Interface().(type) { + case multipart.FileHeader: + value.Set(reflect.ValueOf(*files[0])) + return true, nil + } + case reflect.Slice: + slice := reflect.MakeSlice(value.Type(), len(files), len(files)) + isSetted, err = setArrayOfMultipartFormFiles(slice, field, files) + if err != nil || !isSetted { + return isSetted, err + } + value.Set(slice) + return true, nil + case reflect.Array: + return setArrayOfMultipartFormFiles(value, field, files) + } + return false, errors.New("unsupported field type for multipart.FileHeader") +} + +func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) { + if value.Len() != len(files) { + return false, errors.New("unsupported len of array for []*multipart.FileHeader") + } + for i := range files { + setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1]) + if err != nil || !setted { + return setted, err + } + } + return true, nil +} diff --git a/binding/multipart_form_mapping_test.go b/binding/multipart_form_mapping_test.go new file mode 100644 index 00000000..4c75d1fe --- /dev/null +++ b/binding/multipart_form_mapping_test.go @@ -0,0 +1,138 @@ +// Copyright 2019 Gin Core Team. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package binding + +import ( + "bytes" + "io/ioutil" + "mime/multipart" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFormMultipartBindingBindOneFile(t *testing.T) { + var s struct { + FileValue multipart.FileHeader `form:"file"` + FilePtr *multipart.FileHeader `form:"file"` + SliceValues []multipart.FileHeader `form:"file"` + SlicePtrs []*multipart.FileHeader `form:"file"` + ArrayValues [1]multipart.FileHeader `form:"file"` + ArrayPtrs [1]*multipart.FileHeader `form:"file"` + } + file := testFile{"file", "file1", []byte("hello")} + + req := createRequestMultipartFiles(t, file) + err := FormMultipart.Bind(req, &s) + assert.NoError(t, err) + + assertMultipartFileHeader(t, &s.FileValue, file) + assertMultipartFileHeader(t, s.FilePtr, file) + assert.Len(t, s.SliceValues, 1) + assertMultipartFileHeader(t, &s.SliceValues[0], file) + assert.Len(t, s.SlicePtrs, 1) + assertMultipartFileHeader(t, s.SlicePtrs[0], file) + assertMultipartFileHeader(t, &s.ArrayValues[0], file) + assertMultipartFileHeader(t, s.ArrayPtrs[0], file) +} + +func TestFormMultipartBindingBindTwoFiles(t *testing.T) { + var s struct { + SliceValues []multipart.FileHeader `form:"file"` + SlicePtrs []*multipart.FileHeader `form:"file"` + ArrayValues [2]multipart.FileHeader `form:"file"` + ArrayPtrs [2]*multipart.FileHeader `form:"file"` + } + files := []testFile{ + {"file", "file1", []byte("hello")}, + {"file", "file2", []byte("world")}, + } + + req := createRequestMultipartFiles(t, files...) + err := FormMultipart.Bind(req, &s) + assert.NoError(t, err) + + assert.Len(t, s.SliceValues, len(files)) + assert.Len(t, s.SlicePtrs, len(files)) + assert.Len(t, s.ArrayValues, len(files)) + assert.Len(t, s.ArrayPtrs, len(files)) + + for i, file := range files { + assertMultipartFileHeader(t, &s.SliceValues[i], file) + assertMultipartFileHeader(t, s.SlicePtrs[i], file) + assertMultipartFileHeader(t, &s.ArrayValues[i], file) + assertMultipartFileHeader(t, s.ArrayPtrs[i], file) + } +} + +func TestFormMultipartBindingBindError(t *testing.T) { + files := []testFile{ + {"file", "file1", []byte("hello")}, + {"file", "file2", []byte("world")}, + } + + for _, tt := range []struct { + name string + s interface{} + }{ + {"wrong type", &struct { + Files int `form:"file"` + }{}}, + {"wrong array size", &struct { + Files [1]*multipart.FileHeader `form:"file"` + }{}}, + {"wrong slice type", &struct { + Files []int `form:"file"` + }{}}, + } { + req := createRequestMultipartFiles(t, files...) + err := FormMultipart.Bind(req, tt.s) + assert.Error(t, err) + } +} + +type testFile struct { + Fieldname string + Filename string + Content []byte +} + +func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request { + var body bytes.Buffer + + mw := multipart.NewWriter(&body) + for _, file := range files { + fw, err := mw.CreateFormFile(file.Fieldname, file.Filename) + assert.NoError(t, err) + + n, err := fw.Write(file.Content) + assert.NoError(t, err) + assert.Equal(t, len(file.Content), n) + } + err := mw.Close() + assert.NoError(t, err) + + req, err := http.NewRequest("POST", "/", &body) + assert.NoError(t, err) + + req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary()) + return req +} + +func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) { + assert.Equal(t, file.Filename, fh.Filename) + // assert.Equal(t, int64(len(file.Content)), fh.Size) // fh.Size does not exist on go1.8 + + fl, err := fh.Open() + assert.NoError(t, err) + + body, err := ioutil.ReadAll(fl) + assert.NoError(t, err) + assert.Equal(t, string(file.Content), string(body)) + + err = fl.Close() + assert.NoError(t, err) +} From f98b339b773105aad77f321d0baaa30475bf875d Mon Sep 17 00:00:00 2001 From: guonaihong Date: Thu, 27 Jun 2019 12:47:45 +0800 Subject: [PATCH 286/303] support bind http header param #1956 (#1957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * support bind http header param #1956 update #1956 ``` package main import ( "fmt" "github.com/gin-gonic/gin" ) type testHeader struct { Rate int `header:"Rate"` Domain string `header:"Domain"` } func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { h := testHeader{} if err := c.ShouldBindHeader(&h); err != nil { c.JSON(200, err) } fmt.Printf("%#v\n", h) c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain}) }) r.Run() // client // curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ // output // {"Domain":"music","Rate":300} } ``` * add unit test * Modify the code to get the http header When the http header is obtained in the standard library, the key value will be modified by the CanonicalMIMEHeaderKey function, and finally the value of the http header will be obtained from the map. As follows. ```go func (h MIMEHeader) Get(key string) string {         // ...          v := h[CanonicalMIMEHeaderKey(key)]         // ... } ``` This pr also follows this modification * Thanks to vkd for suggestions, modifying code * Increase test coverage env GOPATH=`pwd` go test github.com/gin-gonic/gin/binding -coverprofile=cover.prof ok github.com/gin-gonic/gin/binding 0.015s coverage: 100.0% of statements * Rollback check code * add use case to README.md --- README.md | 38 +++++++++++++++++++++++++++++++++++ binding/binding.go | 1 + binding/binding_test.go | 25 +++++++++++++++++++++++ binding/header.go | 34 +++++++++++++++++++++++++++++++ context.go | 10 ++++++++++ context_test.go | 44 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+) create mode 100644 binding/header.go diff --git a/README.md b/README.md index 49b044aa..aa5043aa 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Only Bind Query String](#only-bind-query-string) - [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind Uri](#bind-uri) + - [Bind Header](#bind-header) - [Bind HTML checkboxes](#bind-html-checkboxes) - [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering) @@ -910,6 +911,43 @@ $ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3 $ curl -v localhost:8088/thinkerou/not-uuid ``` +### Bind Header + +```go +package main + +import ( + "fmt" + "github.com/gin-gonic/gin" +) + +type testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` +} + +func main() { + r := gin.Default() + r.GET("/", func(c *gin.Context) { + h := testHeader{} + + if err := c.ShouldBindHeader(&h); err != nil { + c.JSON(200, err) + } + + fmt.Printf("%#v\n", h) + c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain}) + }) + + r.Run() + +// client +// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/ +// output +// {"Domain":"music","Rate":300} +} +``` + ### Bind HTML checkboxes See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) diff --git a/binding/binding.go b/binding/binding.go index 520c5109..6d58c3cd 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -78,6 +78,7 @@ var ( MsgPack = msgpackBinding{} YAML = yamlBinding{} Uri = uriBinding{} + Header = headerBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method diff --git a/binding/binding_test.go b/binding/binding_test.go index 6710e42b..827518f9 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -667,6 +667,31 @@ func TestExistsFails(t *testing.T) { assert.Error(t, err) } +func TestHeaderBinding(t *testing.T) { + h := Header + assert.Equal(t, "header", h.Name()) + + type tHeader struct { + Limit int `header:"limit"` + } + + var theader tHeader + req := requestWithBody("GET", "/", "") + req.Header.Add("limit", "1000") + assert.NoError(t, h.Bind(req, &theader)) + assert.Equal(t, 1000, theader.Limit) + + req = requestWithBody("GET", "/", "") + req.Header.Add("fail", `{fail:fail}`) + + type failStruct struct { + Fail map[string]interface{} `header:"fail"` + } + + err := h.Bind(req, &failStruct{}) + assert.Error(t, err) +} + func TestUriBinding(t *testing.T) { b := Uri assert.Equal(t, "uri", b.Name()) diff --git a/binding/header.go b/binding/header.go new file mode 100644 index 00000000..179ce4ea --- /dev/null +++ b/binding/header.go @@ -0,0 +1,34 @@ +package binding + +import ( + "net/http" + "net/textproto" + "reflect" +) + +type headerBinding struct{} + +func (headerBinding) Name() string { + return "header" +} + +func (headerBinding) Bind(req *http.Request, obj interface{}) error { + + if err := mapHeader(obj, req.Header); err != nil { + return err + } + + return validate(obj) +} + +func mapHeader(ptr interface{}, h map[string][]string) error { + return mappingByPtr(ptr, headerSource(h), "header") +} + +type headerSource map[string][]string + +var _ setter = headerSource(nil) + +func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { + return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt) +} diff --git a/context.go b/context.go index 77cdc182..d9fcc285 100644 --- a/context.go +++ b/context.go @@ -583,6 +583,11 @@ func (c *Context) BindYAML(obj interface{}) error { return c.MustBindWith(obj, binding.YAML) } +// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). +func (c *Context) BindHeader(obj interface{}) error { + return c.MustBindWith(obj, binding.Header) +} + // BindUri binds the passed struct pointer using binding.Uri. // It will abort the request with HTTP 400 if any error occurs. func (c *Context) BindUri(obj interface{}) error { @@ -637,6 +642,11 @@ func (c *Context) ShouldBindYAML(obj interface{}) error { return c.ShouldBindWith(obj, binding.YAML) } +// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). +func (c *Context) ShouldBindHeader(obj interface{}) error { + return c.ShouldBindWith(obj, binding.Header) +} + // ShouldBindUri binds the passed struct pointer using the specified binding engine. func (c *Context) ShouldBindUri(obj interface{}) error { m := make(map[string][]string) diff --git a/context_test.go b/context_test.go index b6ecb280..439e8ee6 100644 --- a/context_test.go +++ b/context_test.go @@ -1436,6 +1436,28 @@ func TestContextBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextBindHeader(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("rate", "8000") + c.Request.Header.Add("domain", "music") + c.Request.Header.Add("limit", "1000") + + var testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` + Limit int `header:"limit"` + } + + assert.NoError(t, c.BindHeader(&testHeader)) + assert.Equal(t, 8000, testHeader.Rate) + assert.Equal(t, "music", testHeader.Domain) + assert.Equal(t, 1000, testHeader.Limit) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) @@ -1543,6 +1565,28 @@ func TestContextShouldBindWithXML(t *testing.T) { assert.Equal(t, 0, w.Body.Len()) } +func TestContextShouldBindHeader(t *testing.T) { + w := httptest.NewRecorder() + c, _ := CreateTestContext(w) + + c.Request, _ = http.NewRequest("POST", "/", nil) + c.Request.Header.Add("rate", "8000") + c.Request.Header.Add("domain", "music") + c.Request.Header.Add("limit", "1000") + + var testHeader struct { + Rate int `header:"Rate"` + Domain string `header:"Domain"` + Limit int `header:"limit"` + } + + assert.NoError(t, c.ShouldBindHeader(&testHeader)) + assert.Equal(t, 8000, testHeader.Rate) + assert.Equal(t, "music", testHeader.Domain) + assert.Equal(t, 1000, testHeader.Limit) + assert.Equal(t, 0, w.Body.Len()) +} + func TestContextShouldBindWithQuery(t *testing.T) { w := httptest.NewRecorder() c, _ := CreateTestContext(w) From 31342fc03febd3fb35a269191a40d01311ba87a6 Mon Sep 17 00:00:00 2001 From: guonaihong Date: Fri, 28 Jun 2019 09:25:19 +0800 Subject: [PATCH 287/303] fix README.md code bug and Change map to gin.H (#1963) ``` go func main() { r := gin.Default() // r.GET("/JSONP?callback=x", func(c *gin.Context) { // old r.GET("/JSONP", func(c *gin.Context) { // new data := gin.H{ "foo": "bar", } //callback is x // Will output : x({\"foo\":\"bar\"}) c.JSONP(http.StatusOK, data) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } // client // curl http://127.0.0.1:8080/JSONP?callback=x // old output // 404 page not found // new output // x({"foo":"bar"}) ``` Most of the sample code in the documentation map[string]interface{} is represented by gin.H. gin.H is a very important place for me to like gin, can write a lot less code --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aa5043aa..aa546e58 100644 --- a/README.md +++ b/README.md @@ -1119,8 +1119,8 @@ Using JSONP to request data from a server in a different domain. Add callback t func main() { r := gin.Default() - r.GET("/JSONP?callback=x", func(c *gin.Context) { - data := map[string]interface{}{ + r.GET("/JSONP", func(c *gin.Context) { + data := gin.H{ "foo": "bar", } @@ -1131,6 +1131,9 @@ func main() { // Listen and serve on 0.0.0.0:8080 r.Run(":8080") + + // client + // curl http://127.0.0.1:8080/JSONP?callback=x } ``` @@ -1143,7 +1146,7 @@ func main() { r := gin.Default() r.GET("/someJSON", func(c *gin.Context) { - data := map[string]interface{}{ + data := gin.H{ "lang": "GO语言", "tag": "
", } @@ -1352,7 +1355,7 @@ func main() { router.LoadHTMLFiles("./testdata/template/raw.tmpl") router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ + c.HTML(http.StatusOK, "raw.tmpl", gin.H{ "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), }) }) From 46acb91996921079112470de9068cd4cea503a70 Mon Sep 17 00:00:00 2001 From: srt180 <30768686+srt180@users.noreply.github.com> Date: Fri, 28 Jun 2019 09:34:14 +0800 Subject: [PATCH 288/303] modify readme example code (#1961) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa546e58..f79c0c1c 100644 --- a/README.md +++ b/README.md @@ -756,7 +756,7 @@ func bookableDate( ) bool { if date, ok := field.Interface().(time.Time); ok { today := time.Now() - if today.Year() > date.Year() || today.YearDay() > date.YearDay() { + if today.After(date) { return false } } From fc920dc56141d11599201b2b35838cc9449d5a2a Mon Sep 17 00:00:00 2001 From: Dan Markham Date: Fri, 28 Jun 2019 08:43:07 -0700 Subject: [PATCH 289/303] Drop Support for go1.8 and go1.9 (#1933) --- .travis.yml | 2 -- README.md | 2 +- debug.go | 4 ++-- debug_test.go | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f6ec8a82..27c80ef8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: go matrix: fast_finish: true include: - - go: 1.8.x - - go: 1.9.x - go: 1.10.x - go: 1.11.x env: GO111MODULE=on diff --git a/README.md b/README.md index f79c0c1c..4257df3e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. The first need [Go](https://golang.org/) installed (**version 1.8+ is required**), then you can use the below Go command to install Gin. +1. The first need [Go](https://golang.org/) installed (**version 1.10+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin diff --git a/debug.go b/debug.go index 19e380fb..64777c25 100644 --- a/debug.go +++ b/debug.go @@ -13,7 +13,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 8 +const ginSupportMinGoVer = 10 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -68,7 +68,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon. + debugPrint(`[WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon. `) } diff --git a/debug_test.go b/debug_test.go index 9ace2989..d6f320ef 100644 --- a/debug_test.go +++ b/debug_test.go @@ -91,7 +91,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } From f65018d7b1f1a5d54e5791e9e9ac76e9946eae36 Mon Sep 17 00:00:00 2001 From: bbiao Date: Fri, 28 Jun 2019 23:54:52 +0800 Subject: [PATCH 290/303] Bugfix for the FullPath feature (#1919) * worked with more complex situations * the original pr not work when and a short route with the same prefix to some already added routes --- routes_test.go | 7 ++++++- tree.go | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/routes_test.go b/routes_test.go index 457c923e..0c2f9a0c 100644 --- a/routes_test.go +++ b/routes_test.go @@ -560,10 +560,15 @@ func TestRouteContextHoldsFullPath(t *testing.T) { // Test routes routes := []string{ - "/", "/simple", "/project/:name", + "/", + "/news/home", + "/news", + "/simple-two/one", + "/simple-two/one-two", "/project/:name/build/*params", + "/project/:name/bui", } for _, route := range routes { diff --git a/tree.go b/tree.go index 9a789f2f..371d5ad1 100644 --- a/tree.go +++ b/tree.go @@ -128,6 +128,8 @@ func (n *node) addRoute(path string, handlers HandlersChain) { n.priority++ numParams := countParams(path) + parentFullPathIndex := 0 + // non-empty tree if len(n.path) > 0 || len(n.children) > 0 { walk: @@ -155,7 +157,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { children: n.children, handlers: n.handlers, priority: n.priority - 1, - fullPath: fullPath, + fullPath: n.fullPath, } // Update maxParams (max of all children) @@ -171,6 +173,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { n.path = path[:i] n.handlers = nil n.wildChild = false + n.fullPath = fullPath[:parentFullPathIndex+i] } // Make new node a child of this node @@ -178,6 +181,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { path = path[i:] if n.wildChild { + parentFullPathIndex += len(n.path) n = n.children[0] n.priority++ @@ -211,6 +215,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { // slash after param if n.nType == param && c == '/' && len(n.children) == 1 { + parentFullPathIndex += len(n.path) n = n.children[0] n.priority++ continue walk @@ -219,6 +224,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) { // Check if a child with the next path byte exists for i := 0; i < len(n.indices); i++ { if c == n.indices[i] { + parentFullPathIndex += len(n.path) i = n.incrementChildPrio(i) n = n.children[i] continue walk @@ -369,6 +375,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle // insert remaining path part and handle to the leaf n.path = path[offset:] n.handlers = handlers + n.fullPath = fullPath } // nodeValue holds return values of (*Node).getValue method From 3f53a58d4ad33bf881c028936b0b44f2c3210d56 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Sat, 29 Jun 2019 00:09:53 +0800 Subject: [PATCH 291/303] Add user case: brigade (#1937) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4257df3e..8d7705d3 100644 --- a/README.md +++ b/README.md @@ -2114,3 +2114,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow. * [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares. * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. +* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. From b67bc8f00502b1f555b9db64cd2dfc03098cfc8f Mon Sep 17 00:00:00 2001 From: guonaihong Date: Sat, 29 Jun 2019 20:43:32 +0800 Subject: [PATCH 292/303] Gin1.5 bytes.Buffer to strings.Builder (#1939) * Replace bytes.Buffer to strings.Builder * Merge the latest changes * Update errors.go --- debug.go | 3 +-- errors.go | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/debug.go b/debug.go index 64777c25..49080dbf 100644 --- a/debug.go +++ b/debug.go @@ -5,7 +5,6 @@ package gin import ( - "bytes" "fmt" "html/template" "runtime" @@ -38,7 +37,7 @@ func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { func debugPrintLoadTemplate(tmpl *template.Template) { if IsDebugging() { - var buf bytes.Buffer + var buf strings.Builder for _, tmpl := range tmpl.Templates() { buf.WriteString("\t- ") buf.WriteString(tmpl.Name()) diff --git a/errors.go b/errors.go index 6070ff55..25e8ff60 100644 --- a/errors.go +++ b/errors.go @@ -5,9 +5,9 @@ package gin import ( - "bytes" "fmt" "reflect" + "strings" "github.com/gin-gonic/gin/internal/json" ) @@ -158,7 +158,7 @@ func (a errorMsgs) String() string { if len(a) == 0 { return "" } - var buffer bytes.Buffer + var buffer strings.Builder for i, msg := range a { fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err) if msg.Meta != nil { From 6f7276fdc1d3cfad2052ba770382afd2f4d41dbf Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Sun, 30 Jun 2019 08:55:09 +0800 Subject: [PATCH 293/303] Update CHANGELOG.md (#1966) typo fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ea2495d..15dfb1a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Gin 1.4.0 - [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569) -- [NEW] Refactor of form mapping multipart requesta [#1829](https://github.com/gin-gonic/gin/pull/1829) +- [NEW] Refactor of form mapping multipart request [#1829](https://github.com/gin-gonic/gin/pull/1829) - [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830) - [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802) - [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264) From e602d524cccad90261e10bbb5ca41e9a81e467d4 Mon Sep 17 00:00:00 2001 From: Rafal Zajac Date: Thu, 4 Jul 2019 01:57:52 +0200 Subject: [PATCH 294/303] Typo (#1971) --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index f4532d56..71b80de7 100644 --- a/utils.go +++ b/utils.go @@ -146,6 +146,6 @@ func resolveAddress(addr []string) string { case 1: return addr[0] default: - panic("too much parameters") + panic("too many parameters") } } From 0349de518b3bd862f664d1e58565a3d3bff6a771 Mon Sep 17 00:00:00 2001 From: thinkerou Date: Wed, 10 Jul 2019 06:20:20 +0800 Subject: [PATCH 295/303] upgrade github.com/ugorji/go/codec (#1969) --- go.mod | 5 ++--- go.sum | 15 ++++++--------- vendor/vendor.json | 8 ++++---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 7680d4f7..dbe8afc5 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,11 @@ require ( github.com/gin-contrib/sse v0.1.0 github.com/golang/protobuf v1.3.1 github.com/json-iterator/go v1.1.6 - github.com/mattn/go-isatty v0.0.7 + github.com/mattn/go-isatty v0.0.8 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/stretchr/testify v1.3.0 - github.com/ugorji/go v1.1.4 - golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c + github.com/ugorji/go/codec v1.1.7 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v8 v8.18.2 gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index 8610eae2..c1e9f221 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= @@ -17,15 +17,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= diff --git a/vendor/vendor.json b/vendor/vendor.json index a225eb57..fa8fd13a 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -83,12 +83,12 @@ "versionExact": "v1.2.2" }, { - "checksumSHA1": "csplo594qomjp2IZj82y7mTueOw=", + "checksumSHA1": "S4ei9eSqVThDio0Jn2sav6yUbvg=", "path": "github.com/ugorji/go/codec", - "revision": "2adff0894ba3bc2eeb9f9aea45fefd49802e1a13", - "revisionTime": "2019-04-08T19:08:48Z", + "revision": "82dbfaf494e3b01d2d481376f11f6a5c8cf9599f", + "revisionTime": "2019-07-02T14:15:27Z", "version": "v1.1", - "versionExact": "v1.1.4" + "versionExact": "v1.1.6" }, { "checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=", From 502c898d755b2156b295ea8bdbbecf9ba374d067 Mon Sep 17 00:00:00 2001 From: guonaihong Date: Wed, 10 Jul 2019 13:02:40 +0800 Subject: [PATCH 296/303] binding: support unix time (#1980) * binding: support unix time ref:#1979 * binding: support unix time add test file modify readme ```golang package main import ( "fmt" "github.com/gin-gonic/gin" "time" ) type shareTime struct { CreateTime time.Time `form:"createTime" time_format:"unixNano"` UnixTime time.Time `form:"unixTime" time_format:"unix"` } func main() { r := gin.Default() unix := r.Group("/unix") testCT := time.Date(2019, 7, 6, 16, 0, 33, 123, time.Local) fmt.Printf("%d\n", testCT.UnixNano()) testUT := time.Date(2019, 7, 6, 16, 0, 33, 0, time.Local) fmt.Printf("%d\n", testUT.Unix()) unix.GET("/nano", func(c *gin.Context) { s := shareTime{} c.ShouldBindQuery(&s) if !testCT.Equal(s.CreateTime) { c.String(500, "want %d got %d", testCT.UnixNano(), s.CreateTime) return } c.JSON(200, s) }) unix.GET("/sec", func(c *gin.Context) { s := shareTime{} c.ShouldBindQuery(&s) if !testUT.Equal(s.UnixTime) { c.String(500, "want %d got %d", testCT.Unix(), s.UnixTime) return } c.JSON(200, s) }) r.Run() } ``` * Contraction variable scope --- README.md | 22 +++++++++++++--------- binding/binding_test.go | 41 +++++++++++++++++++++++++++++++++++++---- binding/form_mapping.go | 18 ++++++++++++++++++ 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8d7705d3..2761259e 100644 --- a/README.md +++ b/README.md @@ -846,9 +846,11 @@ import ( ) type Person struct { - Name string `form:"name"` - Address string `form:"address"` - Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + Name string `form:"name"` + Address string `form:"address"` + Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` } func main() { @@ -862,11 +864,13 @@ func startPage(c *gin.Context) { // If `GET`, only `Form` binding engine (`query`) used. // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 - if c.ShouldBind(&person) == nil { - log.Println(person.Name) - log.Println(person.Address) - log.Println(person.Birthday) - } + if c.ShouldBind(&person) == nil { + log.Println(person.Name) + log.Println(person.Address) + log.Println(person.Birthday) + log.Println(person.CreateTime) + log.Println(person.UnixTime) + } c.String(200, "Success") } @@ -874,7 +878,7 @@ func startPage(c *gin.Context) { Test it with: ```sh -$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" +$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033" ``` ### Bind Uri diff --git a/binding/binding_test.go b/binding/binding_test.go index 827518f9..806f3ac9 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -65,8 +65,15 @@ type FooStructUseNumber struct { } type FooBarStructForTimeType struct { - TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` - TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` + TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` + TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` +} + +type FooStructForTimeTypeNotUnixFormat struct { + CreateTime time.Time `form:"createTime" time_format:"unixNano"` + UnixTime time.Time `form:"unixTime" time_format:"unix"` } type FooStructForTimeTypeNotFormat struct { @@ -226,7 +233,10 @@ func TestBindingFormDefaultValue2(t *testing.T) { func TestBindingFormForTime(t *testing.T) { testFormBindingForTime(t, "POST", "/", "/", - "time_foo=2017-11-15&time_bar=", "bar2=foo") + "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo") + testFormBindingForTimeNotUnixFormat(t, "POST", + "/", "/", + "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, "POST", "/", "/", "time_foo=2017-11-15", "bar2=foo") @@ -240,8 +250,11 @@ func TestBindingFormForTime(t *testing.T) { func TestBindingFormForTime2(t *testing.T) { testFormBindingForTime(t, "GET", - "/?time_foo=2017-11-15&time_bar=", "/?bar2=foo", + "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo", "", "") + testFormBindingForTimeNotUnixFormat(t, "POST", + "/", "/", + "time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo") testFormBindingForTimeNotFormat(t, "GET", "/?time_foo=2017-11-15", "/?bar2=foo", "", "") @@ -849,6 +862,8 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String()) assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix()) assert.Equal(t, "UTC", obj.TimeBar.Location().String()) + assert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano()) + assert.Equal(t, int64(1562400033), obj.UnixTime.Unix()) obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) @@ -856,6 +871,24 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s assert.Error(t, err) } +func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, body, badBody string) { + b := Form + assert.Equal(t, "form", b.Name()) + + obj := FooStructForTimeTypeNotUnixFormat{} + req := requestWithBody(method, path, body) + if method == "POST" { + req.Header.Add("Content-Type", MIMEPOSTForm) + } + err := b.Bind(req, &obj) + assert.Error(t, err) + + obj = FooStructForTimeTypeNotUnixFormat{} + req = requestWithBody(method, badPath, badBody) + err = JSON.Bind(req, &obj) + assert.Error(t, err) +} + func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { b := Form assert.Equal(t, "form", b.Name()) diff --git a/binding/form_mapping.go b/binding/form_mapping.go index ebf3b199..80b1d15a 100644 --- a/binding/form_mapping.go +++ b/binding/form_mapping.go @@ -266,6 +266,24 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val timeFormat = time.RFC3339 } + switch tf := strings.ToLower(timeFormat); tf { + case "unix", "unixnano": + tv, err := strconv.ParseInt(val, 10, 0) + if err != nil { + return err + } + + d := time.Duration(1) + if tf == "unixnano" { + d = time.Second + } + + t := time.Unix(tv/int64(d), tv%int64(d)) + value.Set(reflect.ValueOf(t)) + return nil + + } + if val == "" { value.Set(reflect.ValueOf(time.Time{})) return nil From 461df9320ac22d12d19a4e93894c54dd113b60c3 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 27 Jul 2019 03:06:37 +0200 Subject: [PATCH 297/303] Simplify code (#2004) - Use buf.String instead of converison - Remove redundant return --- gin.go | 1 - render/render_test.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gin.go b/gin.go index 220f0401..cbdd080e 100644 --- a/gin.go +++ b/gin.go @@ -437,7 +437,6 @@ func serveError(c *Context, code int, defaultMessage []byte) { return } c.writermem.WriteHeaderNow() - return } func redirectTrailingSlash(c *Context) { diff --git a/render/render_test.go b/render/render_test.go index 9d7eaeef..acdb28ab 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -45,7 +45,7 @@ func TestRenderMsgPack(t *testing.T) { err = codec.NewEncoder(buf, h).Encode(data) assert.NoError(t, err) - assert.Equal(t, w.Body.String(), string(buf.Bytes())) + assert.Equal(t, w.Body.String(), buf.String()) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) } From 20440b96b9ab70ef45346fb2a71f769586969442 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Mon, 5 Aug 2019 04:42:59 +0300 Subject: [PATCH 298/303] Support negative Content-Length in DataFromReader (#1981) You can get an http.Response with ContentLength set to -1 (Chunked encoding), so for DataFromReader to be useful for those we need to support that. --- render/reader.go | 4 +++- render/render_test.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/render/reader.go b/render/reader.go index 312af741..502d9398 100644 --- a/render/reader.go +++ b/render/reader.go @@ -21,7 +21,9 @@ type Reader struct { // Render (Reader) writes data with custom ContentType and headers. func (r Reader) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) - r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) + if r.ContentLength >= 0 { + r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) + } r.writeHeaders(w, r.Headers) _, err = io.Copy(w, r.Reader) return diff --git a/render/render_test.go b/render/render_test.go index acdb28ab..4cc71972 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -498,3 +498,26 @@ func TestRenderReader(t *testing.T) { assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) } + +func TestRenderReaderNoContentLength(t *testing.T) { + w := httptest.NewRecorder() + + body := "#!PNG some raw data" + headers := make(map[string]string) + headers["Content-Disposition"] = `attachment; filename="filename.png"` + headers["x-request-id"] = "requestId" + + err := (Reader{ + ContentLength: -1, + ContentType: "image/png", + Reader: strings.NewReader(body), + Headers: headers, + }).Render(w) + + assert.NoError(t, err) + assert.Equal(t, body, w.Body.String()) + assert.Equal(t, "image/png", w.Header().Get("Content-Type")) + assert.NotContains(t, "Content-Length", w.Header()) + assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) + assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) +} From 5612cadb73b7f2415047e18f15824ed3855feac9 Mon Sep 17 00:00:00 2001 From: Andrew Szeto Date: Fri, 9 Aug 2019 18:26:58 -0700 Subject: [PATCH 299/303] Remove unused code (#2013) --- auth.go | 9 --------- auth_test.go | 7 ------- 2 files changed, 16 deletions(-) diff --git a/auth.go b/auth.go index 9ed81b5d..c96b1e29 100644 --- a/auth.go +++ b/auth.go @@ -5,7 +5,6 @@ package gin import ( - "crypto/subtle" "encoding/base64" "net/http" "strconv" @@ -86,11 +85,3 @@ func authorizationHeader(user, password string) string { base := user + ":" + password return "Basic " + base64.StdEncoding.EncodeToString([]byte(base)) } - -func secureCompare(given, actual string) bool { - if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 { - return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1 - } - // Securely compare actual to itself to keep constant time, but always return false. - return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false -} diff --git a/auth_test.go b/auth_test.go index 197e9208..e44bd100 100644 --- a/auth_test.go +++ b/auth_test.go @@ -81,13 +81,6 @@ func TestBasicAuthAuthorizationHeader(t *testing.T) { assert.Equal(t, "Basic YWRtaW46cGFzc3dvcmQ=", authorizationHeader("admin", "password")) } -func TestBasicAuthSecureCompare(t *testing.T) { - assert.True(t, secureCompare("1234567890", "1234567890")) - assert.False(t, secureCompare("123456789", "1234567890")) - assert.False(t, secureCompare("12345678900", "1234567890")) - assert.False(t, secureCompare("1234567891", "1234567890")) -} - func TestBasicAuthSucceed(t *testing.T) { accounts := Accounts{"admin": "password"} router := New() From 9a820cf0054bcd769f785457b7dbd149a7b29fdd Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Date: Thu, 15 Aug 2019 22:10:44 -0300 Subject: [PATCH 300/303] Bump github.com/mattn/go-isatty library to support Risc-V (#2019) Signed-off-by: CarlosEDP --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index dbe8afc5..849f8c70 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/gin-contrib/sse v0.1.0 github.com/golang/protobuf v1.3.1 github.com/json-iterator/go v1.1.6 - github.com/mattn/go-isatty v0.0.8 + github.com/mattn/go-isatty v0.0.9 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/stretchr/testify v1.3.0 diff --git a/go.sum b/go.sum index c1e9f221..de17ae7d 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= @@ -21,8 +21,8 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= From a22377b09b712d29078d5465391d336047719361 Mon Sep 17 00:00:00 2001 From: Shuo Date: Thu, 29 Aug 2019 08:32:22 +0800 Subject: [PATCH 301/303] logger_test: color (#1926) * logger color: string literals * logger_test: color --- logger.go | 21 +++++++++++---------- logger_test.go | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/logger.go b/logger.go index 5ab4639e..fcf90c25 100644 --- a/logger.go +++ b/logger.go @@ -22,18 +22,19 @@ const ( forceColor ) -var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) - consoleColorMode = autoColor +const ( + green = "\033[97;42m" + white = "\033[90;47m" + yellow = "\033[90;43m" + red = "\033[97;41m" + blue = "\033[97;44m" + magenta = "\033[97;45m" + cyan = "\033[97;46m" + reset = "\033[0m" ) +var consoleColorMode = autoColor + // LoggerConfig defines the config for Logger middleware. type LoggerConfig struct { // Optional. Default value is gin.defaultLogFormatter diff --git a/logger_test.go b/logger_test.go index 9177e1d9..fc53f356 100644 --- a/logger_test.go +++ b/logger_test.go @@ -291,14 +291,14 @@ func TestColorForMethod(t *testing.T) { return p.MethodColor() } - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForMethod("OPTIONS"), "options should be white") - assert.Equal(t, string([]byte{27, 91, 48, 109}), colorForMethod("TRACE"), "trace is not defined and should be the reset color") + assert.Equal(t, blue, colorForMethod("GET"), "get should be blue") + assert.Equal(t, cyan, colorForMethod("POST"), "post should be cyan") + assert.Equal(t, yellow, colorForMethod("PUT"), "put should be yellow") + assert.Equal(t, red, colorForMethod("DELETE"), "delete should be red") + assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green") + assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta") + assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white") + assert.Equal(t, reset, colorForMethod("TRACE"), "trace is not defined and should be the reset color") } func TestColorForStatus(t *testing.T) { @@ -309,10 +309,10 @@ func TestColorForStatus(t *testing.T) { return p.StatusCodeColor() } - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") - assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") - assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") + assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green") + assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white") + assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow") + assert.Equal(t, red, colorForStatus(2), "other things should be red") } func TestResetColor(t *testing.T) { From 6ece26c7c5ce36863599c9a897514e2a70d4021c Mon Sep 17 00:00:00 2001 From: Johnny Dallas Date: Thu, 29 Aug 2019 19:58:55 -0700 Subject: [PATCH 302/303] Add Header bind methods to README (#2025) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2761259e..529746a1 100644 --- a/README.md +++ b/README.md @@ -622,10 +622,10 @@ Note that you need to set the corresponding binding tag on all fields you want t Also, Gin provides two sets of methods for binding: - **Type** - Must bind - - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` + - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader` - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Type** - Should bind - - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML` + - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader` - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. From 01ca625b98910363175cd24b0093c6f14e9d6dd3 Mon Sep 17 00:00:00 2001 From: George Gabolaev Date: Mon, 2 Sep 2019 15:18:08 +0300 Subject: [PATCH 303/303] Fixed JSONP format (added semicolon) (#2007) * Fixed JSONP format (added semicolon) * render_test fix --- context_test.go | 2 +- render/json.go | 2 +- render/render_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/context_test.go b/context_test.go index 439e8ee6..f7bb0f51 100644 --- a/context_test.go +++ b/context_test.go @@ -676,7 +676,7 @@ func TestContextRenderJSONP(t *testing.T) { c.JSONP(http.StatusCreated, H{"foo": "bar"}) assert.Equal(t, http.StatusCreated, w.Code) - assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) + assert.Equal(t, "x({\"foo\":\"bar\"});", w.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) } diff --git a/render/json.go b/render/json.go index 2b07cba0..70506f78 100644 --- a/render/json.go +++ b/render/json.go @@ -138,7 +138,7 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { if err != nil { return err } - _, err = w.Write([]byte(")")) + _, err = w.Write([]byte(");")) if err != nil { return err } diff --git a/render/render_test.go b/render/render_test.go index 4cc71972..b27134ff 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -146,7 +146,7 @@ func TestRenderJsonpJSON(t *testing.T) { err1 := (JsonpJSON{"x", data}).Render(w1) assert.NoError(t, err1) - assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String()) + assert.Equal(t, "x({\"foo\":\"bar\"});", w1.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) w2 := httptest.NewRecorder() @@ -158,7 +158,7 @@ func TestRenderJsonpJSON(t *testing.T) { err2 := (JsonpJSON{"x", datas}).Render(w2) assert.NoError(t, err2) - assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String()) + assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}]);", w2.Body.String()) assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) }