mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-17 22:32:26 +08:00
Merge branch 'master' of https://github.com/gin-gonic/gin into gin-gonic-master
This commit is contained in:
commit
e3f85d8161
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
10
CHANGELOG.md
10
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 (...)
|
||||
|
@ -1,4 +1,4 @@
|
||||
## Contributing
|
||||
## Contributing
|
||||
|
||||
- With issues:
|
||||
- Use the search tool before opening a new issue.
|
||||
|
55
README.md
55
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": "<b>Hello, world!</b>",
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// Serves literal characters
|
||||
r.GET("/purejson", func(c *gin.Context) {
|
||||
c.PureJSON(200, gin.H{
|
||||
"html": "<b>Hello, world!</b>",
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// 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")
|
||||
}
|
||||
```
|
||||
|
3
auth.go
3
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
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
68
binding/default_validator_test.go
Normal file
68
binding/default_validator_test.go
Normal file
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
4
debug.go
4
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+.
|
||||
|
||||
`)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
//go:build go1.13
|
||||
// +build go1.13
|
||||
|
||||
package gin
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
2
mode.go
2
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"))
|
||||
|
277
tree.go
277
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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user