mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-22 01:12:16 +08:00
Merge branch 'master' into remove-go1.6
This commit is contained in:
commit
3ed7f4c54d
12
.travis.yml
12
.travis.yml
@ -1,20 +1,18 @@
|
|||||||
language: go
|
language: go
|
||||||
sudo: false
|
sudo: false
|
||||||
go:
|
|
||||||
- 1.8.x
|
|
||||||
- 1.9.x
|
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
|
||||||
- 1.12.x
|
|
||||||
- master
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
|
- go: 1.8.x
|
||||||
|
- go: 1.9.x
|
||||||
|
- go: 1.10.x
|
||||||
- go: 1.11.x
|
- go: 1.11.x
|
||||||
env: GO111MODULE=on
|
env: GO111MODULE=on
|
||||||
- go: 1.12.x
|
- go: 1.12.x
|
||||||
env: GO111MODULE=on
|
env: GO111MODULE=on
|
||||||
|
- go: master
|
||||||
|
env: GO111MODULE=on
|
||||||
|
|
||||||
git:
|
git:
|
||||||
depth: 10
|
depth: 10
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -57,7 +58,6 @@ type FooStructForTimeTypeFailLocation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FooStructForMapType struct {
|
type FooStructForMapType struct {
|
||||||
// Unknown type: not support map
|
|
||||||
MapFoo map[string]interface{} `form:"map_foo"`
|
MapFoo map[string]interface{} `form:"map_foo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ func TestBindingFormInvalidName2(t *testing.T) {
|
|||||||
func TestBindingFormForType(t *testing.T) {
|
func TestBindingFormForType(t *testing.T) {
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, "POST",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
"map_foo=", "bar2=1", "Map")
|
"map_foo={\"bar\":123}", "map_foo=1", "Map")
|
||||||
|
|
||||||
testFormBindingForType(t, "POST",
|
testFormBindingForType(t, "POST",
|
||||||
"/", "/",
|
"/", "/",
|
||||||
@ -508,20 +508,30 @@ func TestBindingYAMLFail(t *testing.T) {
|
|||||||
`foo:\nbar`, `bar: foo`)
|
`foo:\nbar`, `bar: foo`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFormPostRequest() *http.Request {
|
func createFormPostRequest(t *testing.T) *http.Request {
|
||||||
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
|
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)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultFormPostRequest() *http.Request {
|
func createDefaultFormPostRequest(t *testing.T) *http.Request {
|
||||||
req, _ := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
|
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
|
||||||
|
assert.NoError(t, err)
|
||||||
req.Header.Set("Content-Type", MIMEPOSTForm)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFormPostRequestFail() *http.Request {
|
func createFormPostRequestForMap(t *testing.T) *http.Request {
|
||||||
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=bar"))
|
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)
|
req.Header.Set("Content-Type", MIMEPOSTForm)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
@ -535,26 +545,42 @@ func createFormMultipartRequest(t *testing.T) *http.Request {
|
|||||||
assert.NoError(t, mw.SetBoundary(boundary))
|
assert.NoError(t, mw.SetBoundary(boundary))
|
||||||
assert.NoError(t, mw.WriteField("foo", "bar"))
|
assert.NoError(t, mw.WriteField("foo", "bar"))
|
||||||
assert.NoError(t, mw.WriteField("bar", "foo"))
|
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)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFormMultipartRequestFail(t *testing.T) *http.Request {
|
func createFormMultipartRequestForMap(t *testing.T) *http.Request {
|
||||||
boundary := "--testboundary"
|
boundary := "--testboundary"
|
||||||
body := new(bytes.Buffer)
|
body := new(bytes.Buffer)
|
||||||
mw := multipart.NewWriter(body)
|
mw := multipart.NewWriter(body)
|
||||||
defer mw.Close()
|
defer mw.Close()
|
||||||
|
|
||||||
assert.NoError(t, mw.SetBoundary(boundary))
|
assert.NoError(t, mw.SetBoundary(boundary))
|
||||||
assert.NoError(t, mw.WriteField("map_foo", "bar"))
|
assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}"))
|
||||||
req, _ := http.NewRequest("POST", "/?map_foo=getfoo", body)
|
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)
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary)
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormPost(t *testing.T) {
|
func TestBindingFormPost(t *testing.T) {
|
||||||
req := createFormPostRequest()
|
req := createFormPostRequest(t)
|
||||||
var obj FooBarStruct
|
var obj FooBarStruct
|
||||||
assert.NoError(t, FormPost.Bind(req, &obj))
|
assert.NoError(t, FormPost.Bind(req, &obj))
|
||||||
|
|
||||||
@ -564,7 +590,7 @@ func TestBindingFormPost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingDefaultValueFormPost(t *testing.T) {
|
func TestBindingDefaultValueFormPost(t *testing.T) {
|
||||||
req := createDefaultFormPostRequest()
|
req := createDefaultFormPostRequest(t)
|
||||||
var obj FooDefaultBarStruct
|
var obj FooDefaultBarStruct
|
||||||
assert.NoError(t, FormPost.Bind(req, &obj))
|
assert.NoError(t, FormPost.Bind(req, &obj))
|
||||||
|
|
||||||
@ -572,8 +598,16 @@ func TestBindingDefaultValueFormPost(t *testing.T) {
|
|||||||
assert.Equal(t, "hello", obj.Bar)
|
assert.Equal(t, "hello", obj.Bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormPostFail(t *testing.T) {
|
func TestBindingFormPostForMap(t *testing.T) {
|
||||||
req := createFormPostRequestFail()
|
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
|
var obj FooStructForMapType
|
||||||
err := FormPost.Bind(req, &obj)
|
err := FormPost.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
@ -589,8 +623,18 @@ func TestBindingFormMultipart(t *testing.T) {
|
|||||||
assert.Equal(t, "foo", obj.Bar)
|
assert.Equal(t, "foo", obj.Bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindingFormMultipartFail(t *testing.T) {
|
func TestBindingFormMultipartForMap(t *testing.T) {
|
||||||
req := createFormMultipartRequestFail(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
|
var obj FooStructForMapType
|
||||||
err := FormMultipart.Bind(req, &obj)
|
err := FormMultipart.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
@ -773,6 +817,17 @@ func TestFormBindingFail(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
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) {
|
func TestFormPostBindingFail(t *testing.T) {
|
||||||
b := FormPost
|
b := FormPost
|
||||||
assert.Equal(t, "form-urlencoded", b.Name())
|
assert.Equal(t, "form-urlencoded", b.Name())
|
||||||
@ -1109,7 +1164,8 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
|
|||||||
case "Map":
|
case "Map":
|
||||||
obj := FooStructForMapType{}
|
obj := FooStructForMapType{}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
assert.Error(t, err)
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, float64(123), obj.MapFoo["bar"].(float64))
|
||||||
case "SliceMap":
|
case "SliceMap":
|
||||||
obj := FooStructForSliceMapType{}
|
obj := FooStructForSliceMapType{}
|
||||||
err := b.Bind(req, &obj)
|
err := b.Bind(req, &obj)
|
||||||
@ -1317,3 +1373,88 @@ func TestCanSet(t *testing.T) {
|
|||||||
var c CanSetStruct
|
var c CanSetStruct
|
||||||
assert.Nil(t, mapForm(&c, nil))
|
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)
|
||||||
|
}
|
||||||
|
@ -6,12 +6,17 @@ package binding
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errUnknownType = errors.New("Unknown type")
|
||||||
|
|
||||||
func mapUri(ptr interface{}, m map[string][]string) error {
|
func mapUri(ptr interface{}, m map[string][]string) error {
|
||||||
return mapFormByTag(ptr, m, "uri")
|
return mapFormByTag(ptr, m, "uri")
|
||||||
}
|
}
|
||||||
@ -20,124 +25,165 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
|||||||
return mapFormByTag(ptr, form, "form")
|
return mapFormByTag(ptr, form, "form")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var emptyField = reflect.StructField{}
|
||||||
|
|
||||||
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
||||||
typ := reflect.TypeOf(ptr).Elem()
|
_, err := mapping(reflect.ValueOf(ptr), emptyField, form, tag)
|
||||||
val := reflect.ValueOf(ptr).Elem()
|
return err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
|
func mapping(value reflect.Value, field reflect.StructField, form map[string][]string, tag string) (bool, error) {
|
||||||
switch valueKind {
|
var vKind = value.Kind()
|
||||||
case reflect.Int:
|
|
||||||
return setIntField(val, 0, structField)
|
if vKind == reflect.Ptr {
|
||||||
case reflect.Int8:
|
var isNew bool
|
||||||
return setIntField(val, 8, structField)
|
vPtr := value
|
||||||
case reflect.Int16:
|
if value.IsNil() {
|
||||||
return setIntField(val, 16, structField)
|
isNew = true
|
||||||
case reflect.Int32:
|
vPtr = reflect.New(value.Type().Elem())
|
||||||
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()))
|
|
||||||
}
|
}
|
||||||
structFieldElem := structField.Elem()
|
isSetted, err := mapping(vPtr.Elem(), field, form, tag)
|
||||||
return setWithProperType(structFieldElem.Kind(), val, structFieldElem)
|
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)
|
||||||
|
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:
|
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:
|
||||||
|
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)
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@ -218,3 +264,40 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
|||||||
value.Set(reflect.ValueOf(t))
|
value.Set(reflect.ValueOf(t))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
|
||||||
|
for i, s := range vals {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return str, ""
|
||||||
|
}
|
||||||
|
return str[:idx], str[idx+len(sep):]
|
||||||
|
}
|
||||||
|
61
binding/form_mapping_benchmark_test.go
Normal file
61
binding/form_mapping_benchmark_test.go
Normal file
@ -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)
|
||||||
|
}
|
@ -53,7 +53,7 @@ func (msg *Error) SetMeta(data interface{}) *Error {
|
|||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON creates a properly formated JSON
|
// JSON creates a properly formatted JSON
|
||||||
func (msg *Error) JSON() interface{} {
|
func (msg *Error) JSON() interface{} {
|
||||||
json := H{}
|
json := H{}
|
||||||
if msg.Meta != nil {
|
if msg.Meta != nil {
|
||||||
|
18
gin.go
18
gin.go
@ -225,7 +225,7 @@ func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
|||||||
engine.rebuild405Handlers()
|
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...
|
// 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.
|
// For example, this is the right place for a logger or error management middleware.
|
||||||
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||||
@ -366,10 +366,10 @@ func (engine *Engine) HandleContext(c *Context) {
|
|||||||
|
|
||||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
httpMethod := c.Request.Method
|
httpMethod := c.Request.Method
|
||||||
path := c.Request.URL.Path
|
rPath := c.Request.URL.Path
|
||||||
unescape := false
|
unescape := false
|
||||||
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
|
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
|
||||||
path = c.Request.URL.RawPath
|
rPath = c.Request.URL.RawPath
|
||||||
unescape = engine.UnescapePathValues
|
unescape = engine.UnescapePathValues
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,7 +381,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
}
|
}
|
||||||
root := t[i].root
|
root := t[i].root
|
||||||
// Find route in tree
|
// 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 {
|
if handlers != nil {
|
||||||
c.handlers = handlers
|
c.handlers = handlers
|
||||||
c.Params = params
|
c.Params = params
|
||||||
@ -389,7 +389,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
c.writermem.WriteHeaderNow()
|
c.writermem.WriteHeaderNow()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if httpMethod != "CONNECT" && path != "/" {
|
if httpMethod != "CONNECT" && rPath != "/" {
|
||||||
if tsr && engine.RedirectTrailingSlash {
|
if tsr && engine.RedirectTrailingSlash {
|
||||||
redirectTrailingSlash(c)
|
redirectTrailingSlash(c)
|
||||||
return
|
return
|
||||||
@ -406,7 +406,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
if tree.method == httpMethod {
|
if tree.method == httpMethod {
|
||||||
continue
|
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
|
c.handlers = engine.allNoMethod
|
||||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||||
return
|
return
|
||||||
@ -459,15 +459,15 @@ func redirectTrailingSlash(c *Context) {
|
|||||||
|
|
||||||
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||||
req := c.Request
|
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
|
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||||
if req.Method != "GET" {
|
if req.Method != "GET" {
|
||||||
code = http.StatusTemporaryRedirect
|
code = http.StatusTemporaryRedirect
|
||||||
}
|
}
|
||||||
req.URL.Path = string(fixedPath)
|
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)
|
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||||
c.writermem.WriteHeaderNow()
|
c.writermem.WriteHeaderNow()
|
||||||
return true
|
return true
|
||||||
|
12
go.mod
12
go.mod
@ -1,17 +1,17 @@
|
|||||||
module github.com/gin-gonic/gin
|
module github.com/gin-gonic/gin
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3
|
||||||
github.com/golang/protobuf v1.2.0
|
github.com/golang/protobuf v1.3.0
|
||||||
github.com/json-iterator/go v1.1.5
|
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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43
|
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
|
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95
|
||||||
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/assert.v1 v1.2.1 // indirect
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2
|
gopkg.in/go-playground/validator.v8 v8.18.2
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.2.2
|
||||||
|
31
go.sum
31
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 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
|
||||||
github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
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/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.6 h1:SrwhHcpV4nWrMGdNcC2kXpMfcBVYGDuTArqyhocJgvA=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
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/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/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/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/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 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 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 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA=
|
||||||
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
|
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0 h1:bzeyCHgoAyjZjAhvTpks+qM7sdlh4cCSitmXeCEO3B4=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/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=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
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/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/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 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
|
@ -11,6 +11,8 @@ import "encoding/json"
|
|||||||
var (
|
var (
|
||||||
// Marshal is exported by gin/json package.
|
// Marshal is exported by gin/json package.
|
||||||
Marshal = json.Marshal
|
Marshal = json.Marshal
|
||||||
|
// Unmarshal is exported by gin/json package.
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
// MarshalIndent is exported by gin/json package.
|
// MarshalIndent is exported by gin/json package.
|
||||||
MarshalIndent = json.MarshalIndent
|
MarshalIndent = json.MarshalIndent
|
||||||
// NewDecoder is exported by gin/json package.
|
// NewDecoder is exported by gin/json package.
|
||||||
|
@ -12,6 +12,8 @@ var (
|
|||||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
// Marshal is exported by gin/json package.
|
// Marshal is exported by gin/json package.
|
||||||
Marshal = json.Marshal
|
Marshal = json.Marshal
|
||||||
|
// Unmarshal is exported by gin/json package.
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
// MarshalIndent is exported by gin/json package.
|
// MarshalIndent is exported by gin/json package.
|
||||||
MarshalIndent = json.MarshalIndent
|
MarshalIndent = json.MarshalIndent
|
||||||
// NewDecoder is exported by gin/json package.
|
// NewDecoder is exported by gin/json package.
|
||||||
|
@ -66,6 +66,8 @@ type LogFormatterParams struct {
|
|||||||
IsTerm bool
|
IsTerm bool
|
||||||
// BodySize is the size of the Response Body
|
// BodySize is the size of the Response Body
|
||||||
BodySize int
|
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.
|
// 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{
|
param := LogFormatterParams{
|
||||||
Request: c.Request,
|
Request: c.Request,
|
||||||
IsTerm: isTerm,
|
IsTerm: isTerm,
|
||||||
|
Keys: c.Keys,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop timer
|
// Stop timer
|
||||||
|
@ -181,6 +181,7 @@ func TestLoggerWithFormatter(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoggerWithConfigFormatting(t *testing.T) {
|
func TestLoggerWithConfigFormatting(t *testing.T) {
|
||||||
var gotParam LogFormatterParams
|
var gotParam LogFormatterParams
|
||||||
|
var gotKeys map[string]interface{}
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
router := New()
|
router := New()
|
||||||
@ -204,6 +205,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
|||||||
router.GET("/example", func(c *Context) {
|
router.GET("/example", func(c *Context) {
|
||||||
// set dummy ClientIP
|
// set dummy ClientIP
|
||||||
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
|
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
|
||||||
|
gotKeys = c.Keys
|
||||||
})
|
})
|
||||||
performRequest(router, "GET", "/example?a=100")
|
performRequest(router, "GET", "/example?a=100")
|
||||||
|
|
||||||
@ -223,6 +225,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
|||||||
assert.Equal(t, "GET", gotParam.Method)
|
assert.Equal(t, "GET", gotParam.Method)
|
||||||
assert.Equal(t, "/example?a=100", gotParam.Path)
|
assert.Equal(t, "/example?a=100", gotParam.Path)
|
||||||
assert.Empty(t, gotParam.ErrorMessage)
|
assert.Empty(t, gotParam.ErrorMessage)
|
||||||
|
assert.Equal(t, gotKeys, gotParam.Keys)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
recovery.go
10
recovery.go
@ -52,12 +52,12 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
|||||||
}
|
}
|
||||||
if logger != nil {
|
if logger != nil {
|
||||||
stack := stack(3)
|
stack := stack(3)
|
||||||
httprequest, _ := httputil.DumpRequest(c.Request, false)
|
httpRequest, _ := httputil.DumpRequest(c.Request, false)
|
||||||
if brokenPipe {
|
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() {
|
} else if IsDebugging() {
|
||||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
|
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 {
|
} else {
|
||||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
|
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
|
||||||
timeFormat(time.Now()), err, stack, reset)
|
timeFormat(time.Now()), err, stack, reset)
|
||||||
@ -128,8 +128,8 @@ func function(pc uintptr) []byte {
|
|||||||
// *T.ptrmethod
|
// *T.ptrmethod
|
||||||
// Also the package path might contains dot (e.g. code.google.com/...),
|
// Also the package path might contains dot (e.g. code.google.com/...),
|
||||||
// so first eliminate the path prefix
|
// so first eliminate the path prefix
|
||||||
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
|
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
|
||||||
name = name[lastslash+1:]
|
name = name[lastSlash+1:]
|
||||||
}
|
}
|
||||||
if period := bytes.Index(name, dot); period >= 0 {
|
if period := bytes.Index(name, dot); period >= 0 {
|
||||||
name = name[period+1:]
|
name = name[period+1:]
|
||||||
|
@ -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
|
// Check if file exists and/or if we have permission to access it
|
||||||
if _, err := fs.Open(file); err != nil {
|
if _, err := fs.Open(file); err != nil {
|
||||||
c.Writer.WriteHeader(http.StatusNotFound)
|
c.Writer.WriteHeader(http.StatusNotFound)
|
||||||
c.handlers = group.engine.allNoRoute
|
c.handlers = group.engine.noRoute
|
||||||
// Reset index
|
// Reset index
|
||||||
c.index = -1
|
c.index = -1
|
||||||
return
|
return
|
||||||
|
@ -429,7 +429,6 @@ func TestRouterNotFound(t *testing.T) {
|
|||||||
|
|
||||||
func TestRouterStaticFSNotFound(t *testing.T) {
|
func TestRouterStaticFSNotFound(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
|
|
||||||
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
router.StaticFS("/", http.FileSystem(http.Dir("/thisreallydoesntexist/")))
|
||||||
router.NoRoute(func(c *Context) {
|
router.NoRoute(func(c *Context) {
|
||||||
c.String(404, "non existent")
|
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) {
|
func TestRouteRawPath(t *testing.T) {
|
||||||
route := New()
|
route := New()
|
||||||
route.UseRawPath = true
|
route.UseRawPath = true
|
||||||
|
BIN
testdata/assets/console.png
vendored
Normal file
BIN
testdata/assets/console.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
Loading…
x
Reference in New Issue
Block a user