mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-21 16:58:08 +08:00
Merge branch 'master' into gin1.5-bytes-to-builder
This commit is contained in:
commit
5f4949cf99
@ -3,8 +3,6 @@ language: go
|
|||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- go: 1.8.x
|
|
||||||
- go: 1.9.x
|
|
||||||
- go: 1.10.x
|
- go: 1.10.x
|
||||||
- go: 1.11.x
|
- go: 1.11.x
|
||||||
env: GO111MODULE=on
|
env: GO111MODULE=on
|
||||||
|
92
README.md
92
README.md
@ -40,6 +40,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
- [Only Bind Query String](#only-bind-query-string)
|
- [Only Bind Query String](#only-bind-query-string)
|
||||||
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
||||||
- [Bind Uri](#bind-uri)
|
- [Bind Uri](#bind-uri)
|
||||||
|
- [Bind Header](#bind-header)
|
||||||
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
||||||
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
||||||
- [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering)
|
- [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering)
|
||||||
@ -69,7 +70,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
|||||||
|
|
||||||
To install Gin package, you need to install Go and set your Go workspace first.
|
To install Gin package, you need to install Go and set your Go workspace first.
|
||||||
|
|
||||||
1. The first need [Go](https://golang.org/) installed (**version 1.8+ is required**), then you can use the below Go command to install Gin.
|
1. The first need [Go](https://golang.org/) installed (**version 1.10+ is required**), then you can use the below Go command to install Gin.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ go get -u github.com/gin-gonic/gin
|
$ go get -u github.com/gin-gonic/gin
|
||||||
@ -755,7 +756,7 @@ func bookableDate(
|
|||||||
) bool {
|
) bool {
|
||||||
if date, ok := field.Interface().(time.Time); ok {
|
if date, ok := field.Interface().(time.Time); ok {
|
||||||
today := time.Now()
|
today := time.Now()
|
||||||
if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
|
if today.After(date) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -910,6 +911,43 @@ $ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
|
|||||||
$ curl -v localhost:8088/thinkerou/not-uuid
|
$ curl -v localhost:8088/thinkerou/not-uuid
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bind Header
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testHeader struct {
|
||||||
|
Rate int `header:"Rate"`
|
||||||
|
Domain string `header:"Domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := gin.Default()
|
||||||
|
r.GET("/", func(c *gin.Context) {
|
||||||
|
h := testHeader{}
|
||||||
|
|
||||||
|
if err := c.ShouldBindHeader(&h); err != nil {
|
||||||
|
c.JSON(200, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v\n", h)
|
||||||
|
c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain})
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Run()
|
||||||
|
|
||||||
|
// client
|
||||||
|
// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/
|
||||||
|
// output
|
||||||
|
// {"Domain":"music","Rate":300}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Bind HTML checkboxes
|
### Bind HTML checkboxes
|
||||||
|
|
||||||
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
|
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
|
||||||
@ -959,32 +997,36 @@ result:
|
|||||||
### Multipart/Urlencoded binding
|
### Multipart/Urlencoded binding
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
type ProfileForm struct {
|
||||||
|
Name string `form:"name" binding:"required"`
|
||||||
|
Avatar *multipart.FileHeader `form:"avatar" binding:"required"`
|
||||||
|
|
||||||
import (
|
// or for multiple files
|
||||||
"github.com/gin-gonic/gin"
|
// Avatars []*multipart.FileHeader `form:"avatar" binding:"required"`
|
||||||
)
|
|
||||||
|
|
||||||
type LoginForm struct {
|
|
||||||
User string `form:"user" binding:"required"`
|
|
||||||
Password string `form:"password" binding:"required"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
router.POST("/login", func(c *gin.Context) {
|
router.POST("/profile", func(c *gin.Context) {
|
||||||
// you can bind multipart form with explicit binding declaration:
|
// you can bind multipart form with explicit binding declaration:
|
||||||
// c.ShouldBindWith(&form, binding.Form)
|
// c.ShouldBindWith(&form, binding.Form)
|
||||||
// or you can simply use autobinding with ShouldBind method:
|
// or you can simply use autobinding with ShouldBind method:
|
||||||
var form LoginForm
|
var form ProfileForm
|
||||||
// in this case proper binding will be automatically selected
|
// in this case proper binding will be automatically selected
|
||||||
if c.ShouldBind(&form) == nil {
|
if err := c.ShouldBind(&form); err != nil {
|
||||||
if form.User == "user" && form.Password == "password" {
|
c.String(http.StatusBadRequest, "bad request")
|
||||||
c.JSON(200, gin.H{"status": "you are logged in"})
|
return
|
||||||
} else {
|
|
||||||
c.JSON(401, gin.H{"status": "unauthorized"})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, "unknown error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// db.Save(&form)
|
||||||
|
|
||||||
|
c.String(http.StatusOK, "ok")
|
||||||
})
|
})
|
||||||
router.Run(":8080")
|
router.Run(":8080")
|
||||||
}
|
}
|
||||||
@ -992,7 +1034,7 @@ func main() {
|
|||||||
|
|
||||||
Test it with:
|
Test it with:
|
||||||
```sh
|
```sh
|
||||||
$ curl -v --form user=user --form password=password http://localhost:8080/login
|
$ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile
|
||||||
```
|
```
|
||||||
|
|
||||||
### XML, JSON, YAML and ProtoBuf rendering
|
### XML, JSON, YAML and ProtoBuf rendering
|
||||||
@ -1077,8 +1119,8 @@ Using JSONP to request data from a server in a different domain. Add callback t
|
|||||||
func main() {
|
func main() {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
r.GET("/JSONP?callback=x", func(c *gin.Context) {
|
r.GET("/JSONP", func(c *gin.Context) {
|
||||||
data := map[string]interface{}{
|
data := gin.H{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1089,6 +1131,9 @@ func main() {
|
|||||||
|
|
||||||
// Listen and serve on 0.0.0.0:8080
|
// Listen and serve on 0.0.0.0:8080
|
||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
|
|
||||||
|
// client
|
||||||
|
// curl http://127.0.0.1:8080/JSONP?callback=x
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -1101,7 +1146,7 @@ func main() {
|
|||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
r.GET("/someJSON", func(c *gin.Context) {
|
r.GET("/someJSON", func(c *gin.Context) {
|
||||||
data := map[string]interface{}{
|
data := gin.H{
|
||||||
"lang": "GO语言",
|
"lang": "GO语言",
|
||||||
"tag": "<br>",
|
"tag": "<br>",
|
||||||
}
|
}
|
||||||
@ -1310,7 +1355,7 @@ func main() {
|
|||||||
router.LoadHTMLFiles("./testdata/template/raw.tmpl")
|
router.LoadHTMLFiles("./testdata/template/raw.tmpl")
|
||||||
|
|
||||||
router.GET("/raw", func(c *gin.Context) {
|
router.GET("/raw", func(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
|
c.HTML(http.StatusOK, "raw.tmpl", gin.H{
|
||||||
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -2069,3 +2114,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
|
|||||||
* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow.
|
* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow.
|
||||||
* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares.
|
* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares.
|
||||||
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
||||||
|
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
||||||
|
@ -78,6 +78,7 @@ var (
|
|||||||
MsgPack = msgpackBinding{}
|
MsgPack = msgpackBinding{}
|
||||||
YAML = yamlBinding{}
|
YAML = yamlBinding{}
|
||||||
Uri = uriBinding{}
|
Uri = uriBinding{}
|
||||||
|
Header = headerBinding{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default returns the appropriate Binding instance based on the HTTP method
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
|
@ -667,6 +667,31 @@ func TestExistsFails(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHeaderBinding(t *testing.T) {
|
||||||
|
h := Header
|
||||||
|
assert.Equal(t, "header", h.Name())
|
||||||
|
|
||||||
|
type tHeader struct {
|
||||||
|
Limit int `header:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var theader tHeader
|
||||||
|
req := requestWithBody("GET", "/", "")
|
||||||
|
req.Header.Add("limit", "1000")
|
||||||
|
assert.NoError(t, h.Bind(req, &theader))
|
||||||
|
assert.Equal(t, 1000, theader.Limit)
|
||||||
|
|
||||||
|
req = requestWithBody("GET", "/", "")
|
||||||
|
req.Header.Add("fail", `{fail:fail}`)
|
||||||
|
|
||||||
|
type failStruct struct {
|
||||||
|
Fail map[string]interface{} `header:"fail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.Bind(req, &failStruct{})
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUriBinding(t *testing.T) {
|
func TestUriBinding(t *testing.T) {
|
||||||
b := Uri
|
b := Uri
|
||||||
assert.Equal(t, "uri", b.Name())
|
assert.Equal(t, "uri", b.Name())
|
||||||
|
@ -5,9 +5,7 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultMemory = 32 * 1024 * 1024
|
const defaultMemory = 32 * 1024 * 1024
|
||||||
@ -63,27 +61,3 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
|
|||||||
|
|
||||||
return validate(obj)
|
return validate(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
type multipartRequest http.Request
|
|
||||||
|
|
||||||
var _ setter = (*multipartRequest)(nil)
|
|
||||||
|
|
||||||
var (
|
|
||||||
multipartFileHeaderStructType = reflect.TypeOf(multipart.FileHeader{})
|
|
||||||
)
|
|
||||||
|
|
||||||
// TrySet tries to set a value by the multipart request with the binding a form file
|
|
||||||
func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) {
|
|
||||||
if value.Type() == multipartFileHeaderStructType {
|
|
||||||
_, file, err := (*http.Request)(r).FormFile(key)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if file != nil {
|
|
||||||
value.Set(reflect.ValueOf(*file))
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return setByForm(value, field, r.MultipartForm.Value, key, opt)
|
|
||||||
}
|
|
||||||
|
34
binding/header.go
Normal file
34
binding/header.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type headerBinding struct{}
|
||||||
|
|
||||||
|
func (headerBinding) Name() string {
|
||||||
|
return "header"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (headerBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
|
||||||
|
if err := mapHeader(obj, req.Header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return validate(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapHeader(ptr interface{}, h map[string][]string) error {
|
||||||
|
return mappingByPtr(ptr, headerSource(h), "header")
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerSource map[string][]string
|
||||||
|
|
||||||
|
var _ setter = headerSource(nil)
|
||||||
|
|
||||||
|
func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
|
||||||
|
return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
|
||||||
|
}
|
66
binding/multipart_form_mapping.go
Normal file
66
binding/multipart_form_mapping.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2019 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type multipartRequest http.Request
|
||||||
|
|
||||||
|
var _ setter = (*multipartRequest)(nil)
|
||||||
|
|
||||||
|
// TrySet tries to set a value by the multipart request with the binding a form file
|
||||||
|
func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) {
|
||||||
|
if files := r.MultipartForm.File[key]; len(files) != 0 {
|
||||||
|
return setByMultipartFormFile(value, field, files)
|
||||||
|
}
|
||||||
|
|
||||||
|
return setByForm(value, field, r.MultipartForm.Value, key, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
switch value.Interface().(type) {
|
||||||
|
case *multipart.FileHeader:
|
||||||
|
value.Set(reflect.ValueOf(files[0]))
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
switch value.Interface().(type) {
|
||||||
|
case multipart.FileHeader:
|
||||||
|
value.Set(reflect.ValueOf(*files[0]))
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
slice := reflect.MakeSlice(value.Type(), len(files), len(files))
|
||||||
|
isSetted, err = setArrayOfMultipartFormFiles(slice, field, files)
|
||||||
|
if err != nil || !isSetted {
|
||||||
|
return isSetted, err
|
||||||
|
}
|
||||||
|
value.Set(slice)
|
||||||
|
return true, nil
|
||||||
|
case reflect.Array:
|
||||||
|
return setArrayOfMultipartFormFiles(value, field, files)
|
||||||
|
}
|
||||||
|
return false, errors.New("unsupported field type for multipart.FileHeader")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
|
||||||
|
if value.Len() != len(files) {
|
||||||
|
return false, errors.New("unsupported len of array for []*multipart.FileHeader")
|
||||||
|
}
|
||||||
|
for i := range files {
|
||||||
|
setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
|
||||||
|
if err != nil || !setted {
|
||||||
|
return setted, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
138
binding/multipart_form_mapping_test.go
Normal file
138
binding/multipart_form_mapping_test.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// Copyright 2019 Gin Core Team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormMultipartBindingBindOneFile(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
FileValue multipart.FileHeader `form:"file"`
|
||||||
|
FilePtr *multipart.FileHeader `form:"file"`
|
||||||
|
SliceValues []multipart.FileHeader `form:"file"`
|
||||||
|
SlicePtrs []*multipart.FileHeader `form:"file"`
|
||||||
|
ArrayValues [1]multipart.FileHeader `form:"file"`
|
||||||
|
ArrayPtrs [1]*multipart.FileHeader `form:"file"`
|
||||||
|
}
|
||||||
|
file := testFile{"file", "file1", []byte("hello")}
|
||||||
|
|
||||||
|
req := createRequestMultipartFiles(t, file)
|
||||||
|
err := FormMultipart.Bind(req, &s)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assertMultipartFileHeader(t, &s.FileValue, file)
|
||||||
|
assertMultipartFileHeader(t, s.FilePtr, file)
|
||||||
|
assert.Len(t, s.SliceValues, 1)
|
||||||
|
assertMultipartFileHeader(t, &s.SliceValues[0], file)
|
||||||
|
assert.Len(t, s.SlicePtrs, 1)
|
||||||
|
assertMultipartFileHeader(t, s.SlicePtrs[0], file)
|
||||||
|
assertMultipartFileHeader(t, &s.ArrayValues[0], file)
|
||||||
|
assertMultipartFileHeader(t, s.ArrayPtrs[0], file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormMultipartBindingBindTwoFiles(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
SliceValues []multipart.FileHeader `form:"file"`
|
||||||
|
SlicePtrs []*multipart.FileHeader `form:"file"`
|
||||||
|
ArrayValues [2]multipart.FileHeader `form:"file"`
|
||||||
|
ArrayPtrs [2]*multipart.FileHeader `form:"file"`
|
||||||
|
}
|
||||||
|
files := []testFile{
|
||||||
|
{"file", "file1", []byte("hello")},
|
||||||
|
{"file", "file2", []byte("world")},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := createRequestMultipartFiles(t, files...)
|
||||||
|
err := FormMultipart.Bind(req, &s)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, s.SliceValues, len(files))
|
||||||
|
assert.Len(t, s.SlicePtrs, len(files))
|
||||||
|
assert.Len(t, s.ArrayValues, len(files))
|
||||||
|
assert.Len(t, s.ArrayPtrs, len(files))
|
||||||
|
|
||||||
|
for i, file := range files {
|
||||||
|
assertMultipartFileHeader(t, &s.SliceValues[i], file)
|
||||||
|
assertMultipartFileHeader(t, s.SlicePtrs[i], file)
|
||||||
|
assertMultipartFileHeader(t, &s.ArrayValues[i], file)
|
||||||
|
assertMultipartFileHeader(t, s.ArrayPtrs[i], file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormMultipartBindingBindError(t *testing.T) {
|
||||||
|
files := []testFile{
|
||||||
|
{"file", "file1", []byte("hello")},
|
||||||
|
{"file", "file2", []byte("world")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
s interface{}
|
||||||
|
}{
|
||||||
|
{"wrong type", &struct {
|
||||||
|
Files int `form:"file"`
|
||||||
|
}{}},
|
||||||
|
{"wrong array size", &struct {
|
||||||
|
Files [1]*multipart.FileHeader `form:"file"`
|
||||||
|
}{}},
|
||||||
|
{"wrong slice type", &struct {
|
||||||
|
Files []int `form:"file"`
|
||||||
|
}{}},
|
||||||
|
} {
|
||||||
|
req := createRequestMultipartFiles(t, files...)
|
||||||
|
err := FormMultipart.Bind(req, tt.s)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testFile struct {
|
||||||
|
Fieldname string
|
||||||
|
Filename string
|
||||||
|
Content []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request {
|
||||||
|
var body bytes.Buffer
|
||||||
|
|
||||||
|
mw := multipart.NewWriter(&body)
|
||||||
|
for _, file := range files {
|
||||||
|
fw, err := mw.CreateFormFile(file.Fieldname, file.Filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
n, err := fw.Write(file.Content)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, len(file.Content), n)
|
||||||
|
}
|
||||||
|
err := mw.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/", &body)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+mw.Boundary())
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertMultipartFileHeader(t *testing.T, fh *multipart.FileHeader, file testFile) {
|
||||||
|
assert.Equal(t, file.Filename, fh.Filename)
|
||||||
|
// assert.Equal(t, int64(len(file.Content)), fh.Size) // fh.Size does not exist on go1.8
|
||||||
|
|
||||||
|
fl, err := fh.Open()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(fl)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, string(file.Content), string(body))
|
||||||
|
|
||||||
|
err = fl.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
11
context.go
11
context.go
@ -83,6 +83,7 @@ func (c *Context) reset() {
|
|||||||
c.Errors = c.Errors[0:0]
|
c.Errors = c.Errors[0:0]
|
||||||
c.Accepted = nil
|
c.Accepted = nil
|
||||||
c.queryCache = nil
|
c.queryCache = nil
|
||||||
|
c.formCache = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
||||||
@ -582,6 +583,11 @@ func (c *Context) BindYAML(obj interface{}) error {
|
|||||||
return c.MustBindWith(obj, binding.YAML)
|
return c.MustBindWith(obj, binding.YAML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
|
||||||
|
func (c *Context) BindHeader(obj interface{}) error {
|
||||||
|
return c.MustBindWith(obj, binding.Header)
|
||||||
|
}
|
||||||
|
|
||||||
// BindUri binds the passed struct pointer using binding.Uri.
|
// BindUri binds the passed struct pointer using binding.Uri.
|
||||||
// It will abort the request with HTTP 400 if any error occurs.
|
// It will abort the request with HTTP 400 if any error occurs.
|
||||||
func (c *Context) BindUri(obj interface{}) error {
|
func (c *Context) BindUri(obj interface{}) error {
|
||||||
@ -636,6 +642,11 @@ func (c *Context) ShouldBindYAML(obj interface{}) error {
|
|||||||
return c.ShouldBindWith(obj, binding.YAML)
|
return c.ShouldBindWith(obj, binding.YAML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
|
||||||
|
func (c *Context) ShouldBindHeader(obj interface{}) error {
|
||||||
|
return c.ShouldBindWith(obj, binding.Header)
|
||||||
|
}
|
||||||
|
|
||||||
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
|
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
|
||||||
func (c *Context) ShouldBindUri(obj interface{}) error {
|
func (c *Context) ShouldBindUri(obj interface{}) error {
|
||||||
m := make(map[string][]string)
|
m := make(map[string][]string)
|
||||||
|
@ -1436,6 +1436,28 @@ func TestContextBindWithXML(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextBindHeader(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
|
c.Request.Header.Add("rate", "8000")
|
||||||
|
c.Request.Header.Add("domain", "music")
|
||||||
|
c.Request.Header.Add("limit", "1000")
|
||||||
|
|
||||||
|
var testHeader struct {
|
||||||
|
Rate int `header:"Rate"`
|
||||||
|
Domain string `header:"Domain"`
|
||||||
|
Limit int `header:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, c.BindHeader(&testHeader))
|
||||||
|
assert.Equal(t, 8000, testHeader.Rate)
|
||||||
|
assert.Equal(t, "music", testHeader.Domain)
|
||||||
|
assert.Equal(t, 1000, testHeader.Limit)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextBindWithQuery(t *testing.T) {
|
func TestContextBindWithQuery(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
@ -1543,6 +1565,28 @@ func TestContextShouldBindWithXML(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindHeader(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||||
|
c.Request.Header.Add("rate", "8000")
|
||||||
|
c.Request.Header.Add("domain", "music")
|
||||||
|
c.Request.Header.Add("limit", "1000")
|
||||||
|
|
||||||
|
var testHeader struct {
|
||||||
|
Rate int `header:"Rate"`
|
||||||
|
Domain string `header:"Domain"`
|
||||||
|
Limit int `header:"limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NoError(t, c.ShouldBindHeader(&testHeader))
|
||||||
|
assert.Equal(t, 8000, testHeader.Rate)
|
||||||
|
assert.Equal(t, "music", testHeader.Domain)
|
||||||
|
assert.Equal(t, 1000, testHeader.Limit)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextShouldBindWithQuery(t *testing.T) {
|
func TestContextShouldBindWithQuery(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
|
4
debug.go
4
debug.go
@ -12,7 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ginSupportMinGoVer = 8
|
const ginSupportMinGoVer = 10
|
||||||
|
|
||||||
// IsDebugging returns true if the framework is running in debug mode.
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) {
|
|||||||
|
|
||||||
func debugPrintWARNINGDefault() {
|
func debugPrintWARNINGDefault() {
|
||||||
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
||||||
debugPrint(`[WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.
|
debugPrint(`[WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
|||||||
})
|
})
|
||||||
m, e := getMinVer(runtime.Version())
|
m, e := getMinVer(runtime.Version())
|
||||||
if e == nil && m <= ginSupportMinGoVer {
|
if e == nil && m <= ginSupportMinGoVer {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.8 or later and Go 1.9 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
}
|
}
|
||||||
|
@ -560,10 +560,15 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
|
|||||||
|
|
||||||
// Test routes
|
// Test routes
|
||||||
routes := []string{
|
routes := []string{
|
||||||
"/",
|
|
||||||
"/simple",
|
"/simple",
|
||||||
"/project/:name",
|
"/project/:name",
|
||||||
|
"/",
|
||||||
|
"/news/home",
|
||||||
|
"/news",
|
||||||
|
"/simple-two/one",
|
||||||
|
"/simple-two/one-two",
|
||||||
"/project/:name/build/*params",
|
"/project/:name/build/*params",
|
||||||
|
"/project/:name/bui",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
|
9
tree.go
9
tree.go
@ -128,6 +128,8 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
|||||||
n.priority++
|
n.priority++
|
||||||
numParams := countParams(path)
|
numParams := countParams(path)
|
||||||
|
|
||||||
|
parentFullPathIndex := 0
|
||||||
|
|
||||||
// non-empty tree
|
// non-empty tree
|
||||||
if len(n.path) > 0 || len(n.children) > 0 {
|
if len(n.path) > 0 || len(n.children) > 0 {
|
||||||
walk:
|
walk:
|
||||||
@ -155,7 +157,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
|||||||
children: n.children,
|
children: n.children,
|
||||||
handlers: n.handlers,
|
handlers: n.handlers,
|
||||||
priority: n.priority - 1,
|
priority: n.priority - 1,
|
||||||
fullPath: fullPath,
|
fullPath: n.fullPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update maxParams (max of all children)
|
// Update maxParams (max of all children)
|
||||||
@ -171,6 +173,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
|||||||
n.path = path[:i]
|
n.path = path[:i]
|
||||||
n.handlers = nil
|
n.handlers = nil
|
||||||
n.wildChild = false
|
n.wildChild = false
|
||||||
|
n.fullPath = fullPath[:parentFullPathIndex+i]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make new node a child of this node
|
// Make new node a child of this node
|
||||||
@ -178,6 +181,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
|||||||
path = path[i:]
|
path = path[i:]
|
||||||
|
|
||||||
if n.wildChild {
|
if n.wildChild {
|
||||||
|
parentFullPathIndex += len(n.path)
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
n.priority++
|
n.priority++
|
||||||
|
|
||||||
@ -211,6 +215,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
|||||||
|
|
||||||
// slash after param
|
// slash after param
|
||||||
if n.nType == param && c == '/' && len(n.children) == 1 {
|
if n.nType == param && c == '/' && len(n.children) == 1 {
|
||||||
|
parentFullPathIndex += len(n.path)
|
||||||
n = n.children[0]
|
n = n.children[0]
|
||||||
n.priority++
|
n.priority++
|
||||||
continue walk
|
continue walk
|
||||||
@ -219,6 +224,7 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
|
|||||||
// Check if a child with the next path byte exists
|
// Check if a child with the next path byte exists
|
||||||
for i := 0; i < len(n.indices); i++ {
|
for i := 0; i < len(n.indices); i++ {
|
||||||
if c == n.indices[i] {
|
if c == n.indices[i] {
|
||||||
|
parentFullPathIndex += len(n.path)
|
||||||
i = n.incrementChildPrio(i)
|
i = n.incrementChildPrio(i)
|
||||||
n = n.children[i]
|
n = n.children[i]
|
||||||
continue walk
|
continue walk
|
||||||
@ -369,6 +375,7 @@ func (n *node) insertChild(numParams uint8, path string, fullPath string, handle
|
|||||||
// insert remaining path part and handle to the leaf
|
// insert remaining path part and handle to the leaf
|
||||||
n.path = path[offset:]
|
n.path = path[offset:]
|
||||||
n.handlers = handlers
|
n.handlers = handlers
|
||||||
|
n.fullPath = fullPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeValue holds return values of (*Node).getValue method
|
// nodeValue holds return values of (*Node).getValue method
|
||||||
|
Loading…
x
Reference in New Issue
Block a user