diff --git a/.travis.yml b/.travis.yml index 8ebae712..81662315 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,6 @@ language: go matrix: fast_finish: true include: - - go: 1.12.x - env: GO111MODULE=on - go: 1.13.x - go: 1.13.x env: @@ -17,6 +15,10 @@ matrix: - go: 1.15.x env: - TESTTAGS=nomsgpack + - go: 1.16.x + - go: 1.16.x + env: + - TESTTAGS=nomsgpack - go: master git: diff --git a/README.md b/README.md index d4772d76..6f9178ca 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [Quick start](#quick-start) - [Benchmarks](#benchmarks) - [Gin v1. stable](#gin-v1-stable) - - [Build with jsoniter](#build-with-jsoniter) + - [Build with jsoniter/go-json](#build-with-json-replacement) + - [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature) - [API Examples](#api-examples) - [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options) - [Parameters in path](#parameters-in-path) @@ -84,7 +85,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi To install Gin package, you need to install Go and set your Go workspace first. -1. The first need [Go](https://golang.org/) installed (**version 1.12+ is required**), then you can use the below Go command to install Gin. +1. The first need [Go](https://golang.org/) installed (**version 1.13+ is required**), then you can use the below Go command to install Gin. ```sh $ go get -u github.com/gin-gonic/gin @@ -182,13 +183,28 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr - [x] Battle tested. - [x] API frozen, new releases will not break your code. -## Build with [jsoniter](https://github.com/json-iterator/go) +## Build with json replacement -Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. +Gin uses `encoding/json` as default json package but you can change it by build from other tags. +[jsoniter](https://github.com/json-iterator/go) ```sh $ go build -tags=jsoniter . ``` +[go-json](https://github.com/goccy/go-json) +```sh +$ go build -tags=go_json . +``` + +## Build without `MsgPack` rendering feature + +Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag. + +```sh +$ go build -tags=nomsgpack . +``` + +This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852). ## API Examples @@ -925,7 +941,7 @@ func main() { route.GET("/:name/:id", func(c *gin.Context) { var person Person if err := c.ShouldBindUri(&person); err != nil { - c.JSON(400, gin.H{"msg": err}) + c.JSON(400, gin.H{"msg": err.Error()}) return } c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) @@ -2215,3 +2231,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor * [picfit](https://github.com/thoas/picfit): An image resizing server written in Go. * [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes. * [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system. + diff --git a/binding/json.go b/binding/json.go index d62e0705..45aaa494 100644 --- a/binding/json.go +++ b/binding/json.go @@ -6,7 +6,7 @@ package binding import ( "bytes" - "fmt" + "errors" "io" "net/http" @@ -32,7 +32,7 @@ func (jsonBinding) Name() string { func (jsonBinding) Bind(req *http.Request, obj interface{}) error { if req == nil || req.Body == nil { - return fmt.Errorf("invalid request") + return errors.New("invalid request") } return decodeJSON(req.Body, obj) } diff --git a/context_test.go b/context_test.go index cf3f0be9..993c632f 100644 --- a/context_test.go +++ b/context_test.go @@ -1018,7 +1018,9 @@ func TestContextRenderFile(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") - assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, + // else, Content-Type='text/x-go; charset=utf-8' + assert.NotEqual(t, "", w.Header().Get("Content-Type")) } func TestContextRenderFileFromFS(t *testing.T) { @@ -1030,7 +1032,9 @@ func TestContextRenderFileFromFS(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") - assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, + // else, Content-Type='text/x-go; charset=utf-8' + assert.NotEqual(t, "", w.Header().Get("Content-Type")) assert.Equal(t, "/some/path", c.Request.URL.Path) } @@ -1044,7 +1048,7 @@ func TestContextRenderAttachment(t *testing.T) { assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "func New() *Engine {") - assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.HeaderMap.Get("Content-Disposition")) + assert.Equal(t, fmt.Sprintf("attachment; filename=\"%s\"", newFilename), w.Header().Get("Content-Disposition")) } // TestContextRenderYAML tests that the response is serialized as YAML diff --git a/debug.go b/debug.go index 4c7cd0c3..9bacc685 100644 --- a/debug.go +++ b/debug.go @@ -12,7 +12,7 @@ import ( "strings" ) -const ginSupportMinGoVer = 12 +const ginSupportMinGoVer = 13 // IsDebugging returns true if the framework is running in debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode. @@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) { func debugPrintWARNINGDefault() { if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer { - debugPrint(`[WARNING] Now Gin requires Go 1.12+. + debugPrint(`[WARNING] Now Gin requires Go 1.13+. `) } diff --git a/debug_test.go b/debug_test.go index c2272d0f..05509992 100644 --- a/debug_test.go +++ b/debug_test.go @@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) { }) m, e := getMinVer(runtime.Version()) if e == nil && m <= ginSupportMinGoVer { - assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.12+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) + assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.13+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } else { assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re) } diff --git a/errors_1.13_test.go b/errors_1.13_test.go deleted file mode 100644 index 5fb6057b..00000000 --- a/errors_1.13_test.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -package gin - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" -) - -type TestErr string - -func (e TestErr) Error() string { return string(e) } - -// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()". -// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13, -// hence the "// +build go1.13" directive at the beginning of this file. -func TestErrorUnwrap(t *testing.T) { - innerErr := TestErr("somme error") - - // 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr - err := fmt.Errorf("wrapped: %w", &Error{ - Err: innerErr, - Type: ErrorTypeAny, - }) - - // check that 'errors.Is()' and 'errors.As()' behave as expected : - assert.True(t, errors.Is(err, innerErr)) - var testErr TestErr - assert.True(t, errors.As(err, &testErr)) -} diff --git a/errors_test.go b/errors_test.go index 6aae1c10..ee95ab31 100644 --- a/errors_test.go +++ b/errors_test.go @@ -6,6 +6,7 @@ package gin import ( "errors" + "fmt" "testing" "github.com/gin-gonic/gin/internal/json" @@ -104,3 +105,24 @@ Error #03: third assert.Nil(t, errs.JSON()) assert.Empty(t, errs.String()) } + +type TestErr string + +func (e TestErr) Error() string { return string(e) } + +// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()". +// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13. +func TestErrorUnwrap(t *testing.T) { + innerErr := TestErr("somme error") + + // 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr + err := fmt.Errorf("wrapped: %w", &Error{ + Err: innerErr, + Type: ErrorTypeAny, + }) + + // check that 'errors.Is()' and 'errors.As()' behave as expected : + assert.True(t, errors.Is(err, innerErr)) + var testErr TestErr + assert.True(t, errors.As(err, &testErr)) +} diff --git a/go.sum b/go.sum index a64b3319..ac92ada3 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/goccy/go-json v0.4.11 h1:92nyX606ZN/cUFwctfxwDWm8YWSA38Zlv9s7taFeLyo= +github.com/goccy/go-json v0.4.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/internal/json/go_json.go b/internal/json/go_json.go new file mode 100644 index 00000000..da960571 --- /dev/null +++ b/internal/json/go_json.go @@ -0,0 +1,23 @@ +// Copyright 2017 Bo-Yi Wu. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +//go:build go_json +// +build go_json + +package json + +import json "github.com/goccy/go-json" + +var ( + // Marshal is exported by gin/json package. + Marshal = json.Marshal + // Unmarshal is exported by gin/json package. + Unmarshal = json.Unmarshal + // MarshalIndent is exported by gin/json package. + MarshalIndent = json.MarshalIndent + // NewDecoder is exported by gin/json package. + NewDecoder = json.NewDecoder + // NewEncoder is exported by gin/json package. + NewEncoder = json.NewEncoder +) diff --git a/internal/json/json.go b/internal/json/json.go index 172aeb24..75b60224 100644 --- a/internal/json/json.go +++ b/internal/json/json.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -//go:build !jsoniter -// +build !jsoniter +//go:build !jsoniter && !go_json +// +build !jsoniter,!go_json package json diff --git a/routergroup.go b/routergroup.go index 15d9930d..6f14bf59 100644 --- a/routergroup.go +++ b/routergroup.go @@ -11,6 +11,11 @@ import ( "strings" ) +var ( + // reg match english letters for http method name + regEnLetter = regexp.MustCompile("^[A-Z]+$") +) + // IRouter defines all router handle interface includes single and group router. type IRouter interface { IRoutes @@ -87,7 +92,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl // frequently used, non-standardized or custom methods (e.g. for internal // communication with a proxy). func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes { - if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil { + if matched := regEnLetter.MatchString(httpMethod); !matched { panic("http method " + httpMethod + " is not valid") } return group.handle(httpMethod, relativePath, handlers) diff --git a/routes_test.go b/routes_test.go index 485f0eea..036fa1c3 100644 --- a/routes_test.go +++ b/routes_test.go @@ -360,7 +360,9 @@ func TestRouterMiddlewareAndStatic(t *testing.T) { assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "package gin") - assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type")) + // Content-Type='text/plain; charset=utf-8' when go version <= 1.16, + // else, Content-Type='text/x-go; charset=utf-8' + assert.NotEqual(t, "", w.Header().Get("Content-Type")) assert.NotEqual(t, w.Header().Get("Last-Modified"), "Mon, 02 Jan 2006 15:04:05 MST") assert.Equal(t, "Mon, 02 Jan 2006 15:04:05 MST", w.Header().Get("Expires")) assert.Equal(t, "Gin Framework", w.Header().Get("x-GIN"))