diff --git a/.travis.yml b/.travis.yml
index 0795665d..8ebae712 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ matrix:
env: GO111MODULE=on
- go: 1.13.x
- go: 1.13.x
- env:
+ env:
- TESTTAGS=nomsgpack
- go: 1.14.x
- go: 1.14.x
diff --git a/AUTHORS.md b/AUTHORS.md
index dda19bcf..a477611b 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -156,7 +156,7 @@ People and companies, who have contributed, in alphabetical order.
- Fix variadic parameter in the flexible render API
- Fix Corrupted plain render
- Add Pluggable View Renderer Example
-
+
**@msemenistyi (Mykyta Semenistyi)**
- update Readme.md. Add code to String method
diff --git a/BENCHMARKS.md b/BENCHMARKS.md
index 0f59b509..c11ee99a 100644
--- a/BENCHMARKS.md
+++ b/BENCHMARKS.md
@@ -1,11 +1,11 @@
# Benchmark System
-**VM HOST:** Travis
-**Machine:** Ubuntu 16.04.6 LTS x64
-**Date:** May 04th, 2020
+**VM HOST:** Travis
+**Machine:** Ubuntu 16.04.6 LTS x64
+**Date:** May 04th, 2020
**Version:** Gin v1.6.3
-**Go Version:** 1.14.2 linux/amd64
+**Go Version:** 1.14.2 linux/amd64
**Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)
**Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ac51ad3..ddf30e18 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -215,12 +215,12 @@
## Gin 1.1
-- [NEW] Implement QueryArray and PostArray methods
-- [NEW] Refactor GetQuery and GetPostForm
-- [NEW] Add contribution guide
+- [NEW] Implement QueryArray and PostArray methods
+- [NEW] Refactor GetQuery and GetPostForm
+- [NEW] Add contribution guide
- [FIX] Corrected typos in README
-- [FIX] Removed additional Iota
-- [FIX] Changed imports to gopkg instead of github in README (#733)
+- [FIX] Removed additional Iota
+- [FIX] Changed imports to gopkg instead of github in README (#733)
- [FIX] Logger: skip ANSI color commands if output is not a tty
## Gin 1.0rc2 (...)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 98d758ef..97daa808 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,4 +1,4 @@
-## Contributing
+## Contributing
- With issues:
- Use the search tool before opening a new issue.
diff --git a/README.md b/README.md
index 18b19430..119f9452 100644
--- a/README.md
+++ b/README.md
@@ -103,7 +103,7 @@ import "net/http"
```
## Quick start
-
+
```sh
# assume the following codes in example.go file
$ cat example.go
@@ -588,44 +588,44 @@ func main() {
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
```
-### Controlling Log output coloring
+### Controlling Log output coloring
By default, logs output on console should be colorized depending on the detected TTY.
-Never colorize logs:
+Never colorize logs:
```go
func main() {
// Disable log's color
gin.DisableConsoleColor()
-
+
// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()
-
+
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
-
+
router.Run(":8080")
}
```
-Always colorize logs:
+Always colorize logs:
```go
func main() {
// Force log's color
gin.ForceConsoleColor()
-
+
// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()
-
+
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
-
+
router.Run(":8080")
}
```
@@ -667,12 +667,12 @@ func main() {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
-
+
if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
- }
-
+ }
+
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
@@ -688,12 +688,12 @@ func main() {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
-
+
if xml.User != "manu" || xml.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
- }
-
+ }
+
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
@@ -705,12 +705,12 @@ func main() {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
-
+
if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
- }
-
+ }
+
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
@@ -807,7 +807,7 @@ $ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
-{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
+{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
```
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
@@ -1145,7 +1145,7 @@ func main() {
data := gin.H{
"foo": "bar",
}
-
+
//callback is x
// Will output : x({\"foo\":\"bar\"})
c.JSONP(http.StatusOK, data)
@@ -1190,21 +1190,21 @@ This feature is unavailable in Go 1.6 and lower.
```go
func main() {
r := gin.Default()
-
+
// Serves unicode entities
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{
"html": "Hello, world!",
})
})
-
+
// Serves literal characters
r.GET("/purejson", func(c *gin.Context) {
c.PureJSON(200, gin.H{
"html": "Hello, world!",
})
})
-
+
// listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
@@ -1793,8 +1793,8 @@ func main() {
// Initializing the server in a goroutine so that
// it won't block the graceful shutdown handling below
go func() {
- if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
- log.Fatalf("listen: %s\n", err)
+ if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
+ log.Printf("listen: %s\n", err)
}
}()
@@ -1812,10 +1812,11 @@ func main() {
// the request it is currently handling
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
+
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
-
+
log.Println("Server exiting")
}
```
diff --git a/auth.go b/auth.go
index 43ad36f5..4d8a6ce4 100644
--- a/auth.go
+++ b/auth.go
@@ -5,6 +5,7 @@
package gin
import (
+ "crypto/subtle"
"encoding/base64"
"net/http"
"strconv"
@@ -30,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
return "", false
}
for _, pair := range a {
- if pair.value == authValue {
+ if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 {
return pair.user, true
}
}
diff --git a/binding/binding.go b/binding/binding.go
index 57562845..5caeb581 100644
--- a/binding/binding.go
+++ b/binding/binding.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build !nomsgpack
// +build !nomsgpack
package binding
@@ -51,7 +52,8 @@ type BindingUri interface {
// https://github.com/go-playground/validator/tree/v8.18.2.
type StructValidator interface {
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
- // If the received type is not a struct, any validation should be skipped and nil must be returned.
+ // If the received type is a slice|array, the validation should be performed travel on every element.
+ // If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.
// If the received type is a struct or pointer to a struct, the validation should be performed.
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
// Otherwise nil must be returned.
diff --git a/binding/binding_msgpack_test.go b/binding/binding_msgpack_test.go
index 9791a607..04d94079 100644
--- a/binding/binding_msgpack_test.go
+++ b/binding/binding_msgpack_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build !nomsgpack
// +build !nomsgpack
package binding
diff --git a/binding/binding_nomsgpack.go b/binding/binding_nomsgpack.go
index fd227b11..9afa3dcf 100644
--- a/binding/binding_nomsgpack.go
+++ b/binding/binding_nomsgpack.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build nomsgpack
// +build nomsgpack
package binding
diff --git a/binding/binding_test.go b/binding/binding_test.go
index c354be94..17336177 100644
--- a/binding/binding_test.go
+++ b/binding/binding_test.go
@@ -35,7 +35,7 @@ type QueryTest struct {
}
type FooStruct struct {
- Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required"`
+ Foo string `msgpack:"foo" json:"foo" form:"foo" xml:"foo" binding:"required,max=32"`
}
type FooBarStruct struct {
@@ -181,6 +181,20 @@ func TestBindingJSON(t *testing.T) {
`{"foo": "bar"}`, `{"bar": "foo"}`)
}
+func TestBindingJSONSlice(t *testing.T) {
+ EnableDecoderDisallowUnknownFields = true
+ defer func() {
+ EnableDecoderDisallowUnknownFields = false
+ }()
+
+ testBodyBindingSlice(t, JSON, "json", "/", "/", `[]`, ``)
+ testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{}]`)
+ testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": ""}]`)
+ testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": 123}]`)
+ testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"bar": 123}]`)
+ testBodyBindingSlice(t, JSON, "json", "/", "/", `[{"foo": "123"}]`, `[{"foo": "123456789012345678901234567890123"}]`)
+}
+
func TestBindingJSONUseNumber(t *testing.T) {
testBodyBindingUseNumber(t,
JSON, "json",
@@ -1181,6 +1195,20 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
assert.Error(t, err)
}
+func testBodyBindingSlice(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
+ assert.Equal(t, name, b.Name())
+
+ var obj1 []FooStruct
+ req := requestWithBody("POST", path, body)
+ err := b.Bind(req, &obj1)
+ assert.NoError(t, err)
+
+ var obj2 []FooStruct
+ req = requestWithBody("POST", badPath, badBody)
+ err = JSON.Bind(req, &obj2)
+ assert.Error(t, err)
+}
+
func testBodyBindingStringMap(t *testing.T, b Binding, path, badPath, body, badBody string) {
obj := make(map[string]string)
req := requestWithBody("POST", path, body)
diff --git a/binding/default_validator.go b/binding/default_validator.go
index a4c1a7f6..c57a120f 100644
--- a/binding/default_validator.go
+++ b/binding/default_validator.go
@@ -5,7 +5,9 @@
package binding
import (
+ "fmt"
"reflect"
+ "strings"
"sync"
"github.com/go-playground/validator/v10"
@@ -16,22 +18,54 @@ type defaultValidator struct {
validate *validator.Validate
}
+type sliceValidateError []error
+
+func (err sliceValidateError) Error() string {
+ var errMsgs []string
+ for i, e := range err {
+ if e == nil {
+ continue
+ }
+ errMsgs = append(errMsgs, fmt.Sprintf("[%d]: %s", i, e.Error()))
+ }
+ return strings.Join(errMsgs, "\n")
+}
+
var _ StructValidator = &defaultValidator{}
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
+ if obj == nil {
+ return nil
+ }
+
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 err
+ switch value.Kind() {
+ case reflect.Ptr:
+ return v.ValidateStruct(value.Elem().Interface())
+ case reflect.Struct:
+ return v.validateStruct(obj)
+ case reflect.Slice, reflect.Array:
+ count := value.Len()
+ validateRet := make(sliceValidateError, 0)
+ for i := 0; i < count; i++ {
+ if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
+ validateRet = append(validateRet, err)
+ }
}
+ if len(validateRet) == 0 {
+ return nil
+ }
+ return validateRet
+ default:
+ return nil
}
- return nil
+}
+
+// validateStruct receives struct type
+func (v *defaultValidator) validateStruct(obj interface{}) error {
+ v.lazyinit()
+ return v.validate.Struct(obj)
}
// Engine returns the underlying validator engine which powers the default
diff --git a/binding/default_validator_test.go b/binding/default_validator_test.go
new file mode 100644
index 00000000..e9c6de44
--- /dev/null
+++ b/binding/default_validator_test.go
@@ -0,0 +1,68 @@
+// Copyright 2020 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+ "errors"
+ "testing"
+)
+
+func TestSliceValidateError(t *testing.T) {
+ tests := []struct {
+ name string
+ err sliceValidateError
+ want string
+ }{
+ {"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := tt.err.Error(); got != tt.want {
+ t.Errorf("sliceValidateError.Error() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestDefaultValidator(t *testing.T) {
+ type exampleStruct struct {
+ A string `binding:"max=8"`
+ B int `binding:"gt=0"`
+ }
+ tests := []struct {
+ name string
+ v *defaultValidator
+ obj interface{}
+ wantErr bool
+ }{
+ {"validate nil obj", &defaultValidator{}, nil, false},
+ {"validate int obj", &defaultValidator{}, 3, false},
+ {"validate struct failed-1", &defaultValidator{}, exampleStruct{A: "123456789", B: 1}, true},
+ {"validate struct failed-2", &defaultValidator{}, exampleStruct{A: "12345678", B: 0}, true},
+ {"validate struct passed", &defaultValidator{}, exampleStruct{A: "12345678", B: 1}, false},
+ {"validate *struct failed-1", &defaultValidator{}, &exampleStruct{A: "123456789", B: 1}, true},
+ {"validate *struct failed-2", &defaultValidator{}, &exampleStruct{A: "12345678", B: 0}, true},
+ {"validate *struct passed", &defaultValidator{}, &exampleStruct{A: "12345678", B: 1}, false},
+ {"validate []struct failed-1", &defaultValidator{}, []exampleStruct{{A: "123456789", B: 1}}, true},
+ {"validate []struct failed-2", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 0}}, true},
+ {"validate []struct passed", &defaultValidator{}, []exampleStruct{{A: "12345678", B: 1}}, false},
+ {"validate []*struct failed-1", &defaultValidator{}, []*exampleStruct{{A: "123456789", B: 1}}, true},
+ {"validate []*struct failed-2", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 0}}, true},
+ {"validate []*struct passed", &defaultValidator{}, []*exampleStruct{{A: "12345678", B: 1}}, false},
+ {"validate *[]struct failed-1", &defaultValidator{}, &[]exampleStruct{{A: "123456789", B: 1}}, true},
+ {"validate *[]struct failed-2", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 0}}, true},
+ {"validate *[]struct passed", &defaultValidator{}, &[]exampleStruct{{A: "12345678", B: 1}}, false},
+ {"validate *[]*struct failed-1", &defaultValidator{}, &[]*exampleStruct{{A: "123456789", B: 1}}, true},
+ {"validate *[]*struct failed-2", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 0}}, true},
+ {"validate *[]*struct passed", &defaultValidator{}, &[]*exampleStruct{{A: "12345678", B: 1}}, false},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if err := tt.v.ValidateStruct(tt.obj); (err != nil) != tt.wantErr {
+ t.Errorf("defaultValidator.Validate() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
diff --git a/binding/msgpack.go b/binding/msgpack.go
index a5bc2ad2..2a442996 100644
--- a/binding/msgpack.go
+++ b/binding/msgpack.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build !nomsgpack
// +build !nomsgpack
package binding
diff --git a/binding/msgpack_test.go b/binding/msgpack_test.go
index 296d3eb1..75600ba8 100644
--- a/binding/msgpack_test.go
+++ b/binding/msgpack_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build !nomsgpack
// +build !nomsgpack
package binding
diff --git a/context_appengine.go b/context_appengine.go
index 38c189a0..d5658434 100644
--- a/context_appengine.go
+++ b/context_appengine.go
@@ -1,9 +1,10 @@
-// +build appengine
-
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build appengine
+// +build appengine
+
package gin
func init() {
diff --git a/debug.go b/debug.go
index c66ca440..4c7cd0c3 100644
--- a/debug.go
+++ b/debug.go
@@ -12,7 +12,7 @@ import (
"strings"
)
-const ginSupportMinGoVer = 10
+const ginSupportMinGoVer = 12
// IsDebugging returns true if the framework is running in debug mode.
// Use SetMode(gin.ReleaseMode) to disable debug mode.
@@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) {
func debugPrintWARNINGDefault() {
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
- debugPrint(`[WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.
+ debugPrint(`[WARNING] Now Gin requires Go 1.12+.
`)
}
diff --git a/debug_test.go b/debug_test.go
index d8cd5d1a..c2272d0f 100644
--- a/debug_test.go
+++ b/debug_test.go
@@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
})
m, e := getMinVer(runtime.Version())
if e == nil && m <= ginSupportMinGoVer {
- assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
+ assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.12+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
} else {
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
}
diff --git a/errors_1.13_test.go b/errors_1.13_test.go
index a8f9a94e..5fb6057b 100644
--- a/errors_1.13_test.go
+++ b/errors_1.13_test.go
@@ -1,3 +1,4 @@
+//go:build go1.13
// +build go1.13
package gin
diff --git a/internal/bytesconv/bytesconv.go b/internal/bytesconv/bytesconv.go
index 7b80e335..86e4c4d4 100644
--- a/internal/bytesconv/bytesconv.go
+++ b/internal/bytesconv/bytesconv.go
@@ -5,16 +5,17 @@
package bytesconv
import (
- "reflect"
"unsafe"
)
// StringToBytes converts string to byte slice without a memory allocation.
-func StringToBytes(s string) (b []byte) {
- sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
- bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
- bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len
- return b
+func StringToBytes(s string) []byte {
+ return *(*[]byte)(unsafe.Pointer(
+ &struct {
+ string
+ Cap int
+ }{s, len(s)},
+ ))
}
// BytesToString converts byte slice to string without a memory allocation.
diff --git a/internal/json/json.go b/internal/json/json.go
index 480e8bff..172aeb24 100644
--- a/internal/json/json.go
+++ b/internal/json/json.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build !jsoniter
// +build !jsoniter
package json
diff --git a/internal/json/jsoniter.go b/internal/json/jsoniter.go
index 649a3cdb..232f8dca 100644
--- a/internal/json/jsoniter.go
+++ b/internal/json/jsoniter.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build jsoniter
// +build jsoniter
package json
diff --git a/mode.go b/mode.go
index 11f833e9..c8813aff 100644
--- a/mode.go
+++ b/mode.go
@@ -63,7 +63,7 @@ func SetMode(value string) {
case TestMode:
ginMode = testCode
default:
- panic("gin mode unknown: " + value)
+ panic("gin mode unknown: " + value + " (available mode: debug release test)")
}
modeName = value
diff --git a/render/msgpack.go b/render/msgpack.go
index be2d45c5..6ef5b6e5 100644
--- a/render/msgpack.go
+++ b/render/msgpack.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build !nomsgpack
// +build !nomsgpack
package render
diff --git a/render/render_msgpack_test.go b/render/render_msgpack_test.go
index e439ac48..8170fbe8 100644
--- a/render/render_msgpack_test.go
+++ b/render/render_msgpack_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
+//go:build !nomsgpack
// +build !nomsgpack
package render
diff --git a/routes_test.go b/routes_test.go
index 11ff71a6..485f0eea 100644
--- a/routes_test.go
+++ b/routes_test.go
@@ -238,7 +238,6 @@ func TestRouteParamsByName(t *testing.T) {
assert.True(t, ok)
assert.Equal(t, name, c.Param("name"))
- assert.Equal(t, name, c.Param("name"))
assert.Equal(t, lastName, c.Param("last_name"))
assert.Empty(t, c.Param("wtf"))
@@ -272,7 +271,6 @@ func TestRouteParamsByNameWithExtraSlash(t *testing.T) {
assert.True(t, ok)
assert.Equal(t, name, c.Param("name"))
- assert.Equal(t, name, c.Param("name"))
assert.Equal(t, lastName, c.Param("last_name"))
assert.Empty(t, c.Param("wtf"))
diff --git a/tree.go b/tree.go
index 7a80af9e..74e07e84 100644
--- a/tree.go
+++ b/tree.go
@@ -119,7 +119,6 @@ func (n *node) incrementChildPrio(pos int) int {
for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
// Swap node positions
cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
-
}
// Build new index char string
@@ -559,8 +558,8 @@ func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]by
// Use a static sized buffer on the stack in the common case.
// If the path is too long, allocate a buffer on the heap instead.
buf := make([]byte, 0, stackBufSize)
- if l := len(path) + 1; l > stackBufSize {
- buf = make([]byte, 0, l)
+ if length := len(path) + 1; length > stackBufSize {
+ buf = make([]byte, 0, length)
}
ciPath := n.findCaseInsensitivePathRec(
@@ -600,142 +599,7 @@ walk: // Outer loop for walking the tree
path = path[npLen:]
ciPath = append(ciPath, n.path...)
- if len(path) > 0 {
- // If this node does not have a wildcard (param or catchAll) child,
- // we can just look up the next child node and continue to walk down
- // the tree
- if !n.wildChild {
- // Skip rune bytes already processed
- rb = shiftNRuneBytes(rb, npLen)
-
- if rb[0] != 0 {
- // Old rune not finished
- idxc := rb[0]
- for i, c := range []byte(n.indices) {
- if c == idxc {
- // continue with child node
- n = n.children[i]
- npLen = len(n.path)
- continue walk
- }
- }
- } else {
- // Process a new rune
- var rv rune
-
- // Find rune start.
- // Runes are up to 4 byte long,
- // -4 would definitely be another rune.
- var off int
- for max := min(npLen, 3); off < max; off++ {
- if i := npLen - off; utf8.RuneStart(oldPath[i]) {
- // read rune from cached path
- rv, _ = utf8.DecodeRuneInString(oldPath[i:])
- break
- }
- }
-
- // Calculate lowercase bytes of current rune
- lo := unicode.ToLower(rv)
- utf8.EncodeRune(rb[:], lo)
-
- // Skip already processed bytes
- rb = shiftNRuneBytes(rb, off)
-
- idxc := rb[0]
- for i, c := range []byte(n.indices) {
- // Lowercase matches
- if c == idxc {
- // must use a recursive approach since both the
- // uppercase byte and the lowercase byte might exist
- // as an index
- if out := n.children[i].findCaseInsensitivePathRec(
- path, ciPath, rb, fixTrailingSlash,
- ); out != nil {
- return out
- }
- break
- }
- }
-
- // If we found no match, the same for the uppercase rune,
- // if it differs
- if up := unicode.ToUpper(rv); up != lo {
- utf8.EncodeRune(rb[:], up)
- rb = shiftNRuneBytes(rb, off)
-
- idxc := rb[0]
- for i, c := range []byte(n.indices) {
- // Uppercase matches
- if c == idxc {
- // Continue with child node
- n = n.children[i]
- npLen = len(n.path)
- continue walk
- }
- }
- }
- }
-
- // Nothing found. We can recommend to redirect to the same URL
- // without a trailing slash if a leaf exists for that path
- if fixTrailingSlash && path == "/" && n.handlers != nil {
- return ciPath
- }
- return nil
- }
-
- n = n.children[0]
- switch n.nType {
- case param:
- // Find param end (either '/' or path end)
- end := 0
- for end < len(path) && path[end] != '/' {
- end++
- }
-
- // Add param value to case insensitive path
- ciPath = append(ciPath, path[:end]...)
-
- // We need to go deeper!
- if end < len(path) {
- if len(n.children) > 0 {
- // Continue with child node
- n = n.children[0]
- npLen = len(n.path)
- path = path[end:]
- continue
- }
-
- // ... but we can't
- if fixTrailingSlash && len(path) == end+1 {
- return ciPath
- }
- return nil
- }
-
- if n.handlers != nil {
- return ciPath
- }
-
- if fixTrailingSlash && len(n.children) == 1 {
- // No handle found. Check if a handle for this path + a
- // trailing slash exists
- n = n.children[0]
- if n.path == "/" && n.handlers != nil {
- return append(ciPath, '/')
- }
- }
-
- return nil
-
- case catchAll:
- return append(ciPath, path...)
-
- default:
- panic("invalid node type")
- }
- } else {
+ if len(path) == 0 {
// We should have reached the node containing the handle.
// Check if this node has a handle registered.
if n.handlers != nil {
@@ -758,6 +622,141 @@ walk: // Outer loop for walking the tree
}
return nil
}
+
+ // If this node does not have a wildcard (param or catchAll) child,
+ // we can just look up the next child node and continue to walk down
+ // the tree
+ if !n.wildChild {
+ // Skip rune bytes already processed
+ rb = shiftNRuneBytes(rb, npLen)
+
+ if rb[0] != 0 {
+ // Old rune not finished
+ idxc := rb[0]
+ for i, c := range []byte(n.indices) {
+ if c == idxc {
+ // continue with child node
+ n = n.children[i]
+ npLen = len(n.path)
+ continue walk
+ }
+ }
+ } else {
+ // Process a new rune
+ var rv rune
+
+ // Find rune start.
+ // Runes are up to 4 byte long,
+ // -4 would definitely be another rune.
+ var off int
+ for max := min(npLen, 3); off < max; off++ {
+ if i := npLen - off; utf8.RuneStart(oldPath[i]) {
+ // read rune from cached path
+ rv, _ = utf8.DecodeRuneInString(oldPath[i:])
+ break
+ }
+ }
+
+ // Calculate lowercase bytes of current rune
+ lo := unicode.ToLower(rv)
+ utf8.EncodeRune(rb[:], lo)
+
+ // Skip already processed bytes
+ rb = shiftNRuneBytes(rb, off)
+
+ idxc := rb[0]
+ for i, c := range []byte(n.indices) {
+ // Lowercase matches
+ if c == idxc {
+ // must use a recursive approach since both the
+ // uppercase byte and the lowercase byte might exist
+ // as an index
+ if out := n.children[i].findCaseInsensitivePathRec(
+ path, ciPath, rb, fixTrailingSlash,
+ ); out != nil {
+ return out
+ }
+ break
+ }
+ }
+
+ // If we found no match, the same for the uppercase rune,
+ // if it differs
+ if up := unicode.ToUpper(rv); up != lo {
+ utf8.EncodeRune(rb[:], up)
+ rb = shiftNRuneBytes(rb, off)
+
+ idxc := rb[0]
+ for i, c := range []byte(n.indices) {
+ // Uppercase matches
+ if c == idxc {
+ // Continue with child node
+ n = n.children[i]
+ npLen = len(n.path)
+ continue walk
+ }
+ }
+ }
+ }
+
+ // Nothing found. We can recommend to redirect to the same URL
+ // without a trailing slash if a leaf exists for that path
+ if fixTrailingSlash && path == "/" && n.handlers != nil {
+ return ciPath
+ }
+ return nil
+ }
+
+ n = n.children[0]
+ switch n.nType {
+ case param:
+ // Find param end (either '/' or path end)
+ end := 0
+ for end < len(path) && path[end] != '/' {
+ end++
+ }
+
+ // Add param value to case insensitive path
+ ciPath = append(ciPath, path[:end]...)
+
+ // We need to go deeper!
+ if end < len(path) {
+ if len(n.children) > 0 {
+ // Continue with child node
+ n = n.children[0]
+ npLen = len(n.path)
+ path = path[end:]
+ continue
+ }
+
+ // ... but we can't
+ if fixTrailingSlash && len(path) == end+1 {
+ return ciPath
+ }
+ return nil
+ }
+
+ if n.handlers != nil {
+ return ciPath
+ }
+
+ if fixTrailingSlash && len(n.children) == 1 {
+ // No handle found. Check if a handle for this path + a
+ // trailing slash exists
+ n = n.children[0]
+ if n.path == "/" && n.handlers != nil {
+ return append(ciPath, '/')
+ }
+ }
+
+ return nil
+
+ case catchAll:
+ return append(ciPath, path...)
+
+ default:
+ panic("invalid node type")
+ }
}
// Nothing found.