Merge branch 'master' into fix_1942_ignore_walking

This commit is contained in:
thinkerou 2019-10-31 23:23:50 +08:00 committed by GitHub
commit 697335b2d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 963 additions and 310 deletions

View File

@ -3,11 +3,47 @@
- Please provide source code and commit sha if you found a bug. - Please provide source code and commit sha if you found a bug.
- Review existing issues and provide feedback or react to them. - Review existing issues and provide feedback or react to them.
## Description
<!-- Description of a problem -->
## How to reproduce
<!-- The smallest possible code example to show the problem that can be compiled, like -->
```
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
g := gin.Default()
g.GET("/hello/:name", func(c *gin.Context) {
c.String(200, "Hello %s", c.Param("name"))
})
g.Run(":9000")
}
```
## Expectations
<!-- Your expectation result of 'curl' command, like -->
```
$ curl http://localhost:8201/hello/world
Hello world
```
## Actual result
<!-- Actual result showing the problem -->
```
$ curl -i http://localhost:8201/hello/world
<YOUR RESULT>
```
## Environment
- go version: - go version:
- gin version (or commit ref): - gin version (or commit ref):
- operating system: - operating system:
## Description
## Screenshots

View File

@ -3,15 +3,13 @@ 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
- go: 1.12.x - go: 1.12.x
env: GO111MODULE=on env: GO111MODULE=on
- go: 1.13.x
- go: master - go: master
env: GO111MODULE=on
git: git:
depth: 10 depth: 10

View File

@ -2,7 +2,7 @@
### Gin 1.4.0 ### Gin 1.4.0
- [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569) - [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569)
- [NEW] Refactor of form mapping multipart requesta [#1829](https://github.com/gin-gonic/gin/pull/1829) - [NEW] Refactor of form mapping multipart request [#1829](https://github.com/gin-gonic/gin/pull/1829)
- [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830) - [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830)
- [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802) - [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802)
- [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264) - [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264)
@ -15,7 +15,7 @@
- [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749) - [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749)
- [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252) - [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252)
- [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775) - [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775)
- [NEW] Extend context.File to allow for the content-dispositon attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260) - [NEW] Extend context.File to allow for the content-disposition attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260)
- [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112) - [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112)
- [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238) - [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238)
- [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020) - [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020)
@ -231,7 +231,7 @@
- [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults. - [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults.
- [NEW] Flexible rendering API - [NEW] Flexible rendering API
- [NEW] Add Context.File() - [NEW] Add Context.File()
- [NEW] Add shorcut RunTLS() for http.ListenAndServeTLS - [NEW] Add shortcut RunTLS() for http.ListenAndServeTLS
- [FIX] Rename NotFound404() to NoRoute() - [FIX] Rename NotFound404() to NoRoute()
- [FIX] Errors in context are purged - [FIX] Errors in context are purged
- [FIX] Adds HEAD method in Static file serving - [FIX] Adds HEAD method in Static file serving
@ -254,7 +254,7 @@
- [NEW] New Bind() and BindWith() methods for parsing request body. - [NEW] New Bind() and BindWith() methods for parsing request body.
- [NEW] Add Content.Copy() - [NEW] Add Content.Copy()
- [NEW] Add context.LastError() - [NEW] Add context.LastError()
- [NEW] Add shorcut for OPTIONS HTTP method - [NEW] Add shortcut for OPTIONS HTTP method
- [FIX] Tons of README fixes - [FIX] Tons of README fixes
- [FIX] Header is written before body - [FIX] Header is written before body
- [FIX] BasicAuth() and changes API a little bit - [FIX] BasicAuth() and changes API a little bit

143
README.md
View File

@ -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
@ -100,6 +101,12 @@ $ go get github.com/kardianos/govendor
$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_" $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
``` ```
If you are on a Mac and you're installing Go 1.8 (released: Feb 2017) or later, GOPATH is automatically determined by the Go toolchain for you. It defaults to $HOME/go on macOS so you can create your project like this
```sh
$ mkdir -p $HOME/go/src/github.com/myusername/project && cd "$_"
```
3. Vendor init your project and add gin 3. Vendor init your project and add gin
```sh ```sh
@ -138,12 +145,12 @@ func main() {
"message": "pong", "message": "pong",
}) })
}) })
r.Run() // listen and serve on 0.0.0.0:8080 r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
} }
``` ```
``` ```
# run example.go and visit 0.0.0.0:8080/ping on browser # run example.go and visit 0.0.0.0:8080/ping (for windows "localhost:8080/ping") on browser
$ go run example.go $ go run example.go
``` ```
@ -621,10 +628,10 @@ Note that you need to set the corresponding binding tag on all fields you want t
Also, Gin provides two sets of methods for binding: Also, Gin provides two sets of methods for binding:
- **Type** - Must bind - **Type** - Must bind
- **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML` - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
- **Type** - Should bind - **Type** - Should bind
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML` - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
@ -755,7 +762,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
} }
} }
@ -845,9 +852,11 @@ import (
) )
type Person struct { type Person struct {
Name string `form:"name"` Name string `form:"name"`
Address string `form:"address"` Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"` Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
UnixTime time.Time `form:"unixTime" time_format:"unix"`
} }
func main() { func main() {
@ -861,11 +870,13 @@ func startPage(c *gin.Context) {
// If `GET`, only `Form` binding engine (`query`) used. // If `GET`, only `Form` binding engine (`query`) used.
// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`). // If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48 // See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
if c.ShouldBind(&person) == nil { if c.ShouldBind(&person) == nil {
log.Println(person.Name) log.Println(person.Name)
log.Println(person.Address) log.Println(person.Address)
log.Println(person.Birthday) log.Println(person.Birthday)
} log.Println(person.CreateTime)
log.Println(person.UnixTime)
}
c.String(200, "Success") c.String(200, "Success")
} }
@ -873,7 +884,7 @@ func startPage(c *gin.Context) {
Test it with: Test it with:
```sh ```sh
$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" $ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
``` ```
### Bind Uri ### Bind Uri
@ -910,6 +921,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 +1007,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 +1044,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 +1129,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,19 +1141,22 @@ 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
} }
``` ```
#### AsciiJSON #### AsciiJSON
Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII chracters. Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters.
```go ```go
func main() { 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 +1365,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),
}) })
}) })
@ -1623,11 +1678,19 @@ func main() {
} }
g.Go(func() error { g.Go(func() error {
return server01.ListenAndServe() err := server01.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
return err
}) })
g.Go(func() error { g.Go(func() error {
return server02.ListenAndServe() err := server02.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
return err
}) })
if err := g.Wait(); err != nil { if err := g.Wait(); err != nil {
@ -1744,6 +1807,7 @@ func main() {
func loadTemplate() (*template.Template, error) { func loadTemplate() (*template.Template, error) {
t := template.New("") t := template.New("")
for name, file := range Assets.Files { for name, file := range Assets.Files {
defer file.Close()
if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
continue continue
} }
@ -2069,3 +2133,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.

View File

@ -5,7 +5,6 @@
package gin package gin
import ( import (
"crypto/subtle"
"encoding/base64" "encoding/base64"
"net/http" "net/http"
"strconv" "strconv"
@ -86,11 +85,3 @@ func authorizationHeader(user, password string) string {
base := user + ":" + password base := user + ":" + password
return "Basic " + base64.StdEncoding.EncodeToString([]byte(base)) return "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
} }
func secureCompare(given, actual string) bool {
if subtle.ConstantTimeEq(int32(len(given)), int32(len(actual))) == 1 {
return subtle.ConstantTimeCompare([]byte(given), []byte(actual)) == 1
}
// Securely compare actual to itself to keep constant time, but always return false.
return subtle.ConstantTimeCompare([]byte(actual), []byte(actual)) == 1 && false
}

View File

@ -81,13 +81,6 @@ func TestBasicAuthAuthorizationHeader(t *testing.T) {
assert.Equal(t, "Basic YWRtaW46cGFzc3dvcmQ=", authorizationHeader("admin", "password")) assert.Equal(t, "Basic YWRtaW46cGFzc3dvcmQ=", authorizationHeader("admin", "password"))
} }
func TestBasicAuthSecureCompare(t *testing.T) {
assert.True(t, secureCompare("1234567890", "1234567890"))
assert.False(t, secureCompare("123456789", "1234567890"))
assert.False(t, secureCompare("12345678900", "1234567890"))
assert.False(t, secureCompare("1234567891", "1234567890"))
}
func TestBasicAuthSucceed(t *testing.T) { func TestBasicAuthSucceed(t *testing.T) {
accounts := Accounts{"admin": "password"} accounts := Accounts{"admin": "password"}
router := New() router := New()

View File

@ -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

View File

@ -1,72 +0,0 @@
package binding
import (
"bytes"
"io/ioutil"
"testing"
"github.com/gin-gonic/gin/testdata/protoexample"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec"
)
func TestBindingBody(t *testing.T) {
for _, tt := range []struct {
name string
binding BindingBody
body string
want string
}{
{
name: "JSON binding",
binding: JSON,
body: `{"foo":"FOO"}`,
},
{
name: "XML binding",
binding: XML,
body: `<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>FOO</foo>
</root>`,
},
{
name: "MsgPack binding",
binding: MsgPack,
body: msgPackBody(t),
},
{
name: "YAML binding",
binding: YAML,
body: `foo: FOO`,
},
} {
t.Logf("testing: %s", tt.name)
req := requestWithBody("POST", "/", tt.body)
form := FooStruct{}
body, _ := ioutil.ReadAll(req.Body)
assert.NoError(t, tt.binding.BindBody(body, &form))
assert.Equal(t, FooStruct{"FOO"}, form)
}
}
func msgPackBody(t *testing.T) string {
test := FooStruct{"FOO"}
h := new(codec.MsgpackHandle)
buf := bytes.NewBuffer(nil)
assert.NoError(t, codec.NewEncoder(buf, h).Encode(test))
return buf.String()
}
func TestBindingBodyProto(t *testing.T) {
test := protoexample.Test{
Label: proto.String("FOO"),
}
data, _ := proto.Marshal(&test)
req := requestWithBody("POST", "/", string(data))
form := protoexample.Test{}
body, _ := ioutil.ReadAll(req.Body)
assert.NoError(t, ProtoBuf.BindBody(body, &form))
assert.Equal(t, test, form)
}

View File

@ -64,9 +64,20 @@ type FooStructUseNumber struct {
Foo interface{} `json:"foo" binding:"required"` Foo interface{} `json:"foo" binding:"required"`
} }
type FooStructDisallowUnknownFields struct {
Foo interface{} `json:"foo" binding:"required"`
}
type FooBarStructForTimeType struct { type FooBarStructForTimeType struct {
TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"` TimeFoo time.Time `form:"time_foo" time_format:"2006-01-02" time_utc:"1" time_location:"Asia/Chongqing"`
TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"` TimeBar time.Time `form:"time_bar" time_format:"2006-01-02" time_utc:"1"`
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
UnixTime time.Time `form:"unixTime" time_format:"unix"`
}
type FooStructForTimeTypeNotUnixFormat struct {
CreateTime time.Time `form:"createTime" time_format:"unixNano"`
UnixTime time.Time `form:"unixTime" time_format:"unix"`
} }
type FooStructForTimeTypeNotFormat struct { type FooStructForTimeTypeNotFormat struct {
@ -187,6 +198,12 @@ func TestBindingJSONUseNumber2(t *testing.T) {
`{"foo": 123}`, `{"bar": "foo"}`) `{"foo": 123}`, `{"bar": "foo"}`)
} }
func TestBindingJSONDisallowUnknownFields(t *testing.T) {
testBodyBindingDisallowUnknownFields(t, JSON,
"/", "/",
`{"foo": "bar"}`, `{"foo": "bar", "what": "this"}`)
}
func TestBindingForm(t *testing.T) { func TestBindingForm(t *testing.T) {
testFormBinding(t, "POST", testFormBinding(t, "POST",
"/", "/", "/", "/",
@ -226,7 +243,10 @@ func TestBindingFormDefaultValue2(t *testing.T) {
func TestBindingFormForTime(t *testing.T) { func TestBindingFormForTime(t *testing.T) {
testFormBindingForTime(t, "POST", testFormBindingForTime(t, "POST",
"/", "/", "/", "/",
"time_foo=2017-11-15&time_bar=", "bar2=foo") "time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "bar2=foo")
testFormBindingForTimeNotUnixFormat(t, "POST",
"/", "/",
"time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo")
testFormBindingForTimeNotFormat(t, "POST", testFormBindingForTimeNotFormat(t, "POST",
"/", "/", "/", "/",
"time_foo=2017-11-15", "bar2=foo") "time_foo=2017-11-15", "bar2=foo")
@ -240,8 +260,11 @@ func TestBindingFormForTime(t *testing.T) {
func TestBindingFormForTime2(t *testing.T) { func TestBindingFormForTime2(t *testing.T) {
testFormBindingForTime(t, "GET", testFormBindingForTime(t, "GET",
"/?time_foo=2017-11-15&time_bar=", "/?bar2=foo", "/?time_foo=2017-11-15&time_bar=&createTime=1562400033000000123&unixTime=1562400033", "/?bar2=foo",
"", "") "", "")
testFormBindingForTimeNotUnixFormat(t, "POST",
"/", "/",
"time_foo=2017-11-15&createTime=bad&unixTime=bad", "bar2=foo")
testFormBindingForTimeNotFormat(t, "GET", testFormBindingForTimeNotFormat(t, "GET",
"/?time_foo=2017-11-15", "/?bar2=foo", "/?time_foo=2017-11-15", "/?bar2=foo",
"", "") "", "")
@ -418,7 +441,8 @@ func createFormFilesMultipartRequest(t *testing.T) *http.Request {
defer f.Close() defer f.Close()
fw, err1 := mw.CreateFormFile("file", "form.go") fw, err1 := mw.CreateFormFile("file", "form.go")
assert.NoError(t, err1) assert.NoError(t, err1)
io.Copy(fw, f) _, err = io.Copy(fw, f)
assert.NoError(t, err)
req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
assert.NoError(t, err2) assert.NoError(t, err2)
@ -442,7 +466,8 @@ func createFormFilesMultipartRequestFail(t *testing.T) *http.Request {
defer f.Close() defer f.Close()
fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go") fw, err1 := mw.CreateFormFile("file_foo", "form_foo.go")
assert.NoError(t, err1) assert.NoError(t, err1)
io.Copy(fw, f) _, err = io.Copy(fw, f)
assert.NoError(t, err)
req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
assert.NoError(t, err2) assert.NoError(t, err2)
@ -531,7 +556,8 @@ func TestBindingFormPostForMapFail(t *testing.T) {
func TestBindingFormFilesMultipart(t *testing.T) { func TestBindingFormFilesMultipart(t *testing.T) {
req := createFormFilesMultipartRequest(t) req := createFormFilesMultipartRequest(t)
var obj FooBarFileStruct var obj FooBarFileStruct
FormMultipart.Bind(req, &obj) err := FormMultipart.Bind(req, &obj)
assert.NoError(t, err)
// file from os // file from os
f, _ := os.Open("form.go") f, _ := os.Open("form.go")
@ -645,9 +671,9 @@ func TestValidationDisabled(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestExistsSucceeds(t *testing.T) { func TestRequiredSucceeds(t *testing.T) {
type HogeStruct struct { type HogeStruct struct {
Hoge *int `json:"hoge" binding:"exists"` Hoge *int `json:"hoge" binding:"required"`
} }
var obj HogeStruct var obj HogeStruct
@ -656,9 +682,9 @@ func TestExistsSucceeds(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestExistsFails(t *testing.T) { func TestRequiredFails(t *testing.T) {
type HogeStruct struct { type HogeStruct struct {
Hoge *int `json:"foo" binding:"exists"` Hoge *int `json:"foo" binding:"required"`
} }
var obj HogeStruct var obj HogeStruct
@ -667,6 +693,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())
@ -824,6 +875,8 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s
assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String()) assert.Equal(t, "Asia/Chongqing", obj.TimeFoo.Location().String())
assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix()) assert.Equal(t, int64(-62135596800), obj.TimeBar.Unix())
assert.Equal(t, "UTC", obj.TimeBar.Location().String()) assert.Equal(t, "UTC", obj.TimeBar.Location().String())
assert.Equal(t, int64(1562400033000000123), obj.CreateTime.UnixNano())
assert.Equal(t, int64(1562400033), obj.UnixTime.Unix())
obj = FooBarStructForTimeType{} obj = FooBarStructForTimeType{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
@ -831,6 +884,24 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s
assert.Error(t, err) assert.Error(t, err)
} }
func testFormBindingForTimeNotUnixFormat(t *testing.T, method, path, badPath, body, badBody string) {
b := Form
assert.Equal(t, "form", b.Name())
obj := FooStructForTimeTypeNotUnixFormat{}
req := requestWithBody(method, path, body)
if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm)
}
err := b.Bind(req, &obj)
assert.Error(t, err)
obj = FooStructForTimeTypeNotUnixFormat{}
req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj)
assert.Error(t, err)
}
func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) { func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, badBody string) {
b := Form b := Form
assert.Equal(t, "form", b.Name()) assert.Equal(t, "form", b.Name())
@ -1104,6 +1175,25 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod
assert.Error(t, err) assert.Error(t, err)
} }
func testBodyBindingDisallowUnknownFields(t *testing.T, b Binding, path, badPath, body, badBody string) {
EnableDecoderDisallowUnknownFields = true
defer func() {
EnableDecoderDisallowUnknownFields = false
}()
obj := FooStructDisallowUnknownFields{}
req := requestWithBody("POST", path, body)
err := b.Bind(req, &obj)
assert.NoError(t, err)
assert.Equal(t, "bar", obj.Foo)
obj = FooStructDisallowUnknownFields{}
req = requestWithBody("POST", badPath, badBody)
err = JSON.Bind(req, &obj)
assert.Error(t, err)
assert.Contains(t, err.Error(), "what")
}
func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())

View File

@ -8,7 +8,7 @@ import (
"reflect" "reflect"
"sync" "sync"
"gopkg.in/go-playground/validator.v8" "gopkg.in/go-playground/validator.v9"
) )
type defaultValidator struct { type defaultValidator struct {
@ -45,7 +45,7 @@ func (v *defaultValidator) Engine() interface{} {
func (v *defaultValidator) lazyinit() { func (v *defaultValidator) lazyinit() {
v.once.Do(func() { v.once.Do(func() {
config := &validator.Config{TagName: "binding"} v.validate = validator.New()
v.validate = validator.New(config) v.validate.SetTagName("binding")
}) })
} }

View File

@ -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)
}

View File

@ -15,7 +15,7 @@ import (
"github.com/gin-gonic/gin/internal/json" "github.com/gin-gonic/gin/internal/json"
) )
var errUnknownType = errors.New("Unknown type") 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")
@ -267,6 +267,24 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
timeFormat = time.RFC3339 timeFormat = time.RFC3339
} }
switch tf := strings.ToLower(timeFormat); tf {
case "unix", "unixnano":
tv, err := strconv.ParseInt(val, 10, 0)
if err != nil {
return err
}
d := time.Duration(1)
if tf == "unixnano" {
d = time.Second
}
t := time.Unix(tv/int64(d), tv%int64(d))
value.Set(reflect.ValueOf(t))
return nil
}
if val == "" { if val == "" {
value.Set(reflect.ValueOf(time.Time{})) value.Set(reflect.ValueOf(time.Time{}))
return nil return nil

View File

@ -32,7 +32,10 @@ type structFull struct {
func BenchmarkMapFormFull(b *testing.B) { func BenchmarkMapFormFull(b *testing.B) {
var s structFull var s structFull
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
mapForm(&s, form) err := mapForm(&s, form)
if err != nil {
b.Fatalf("Error on a form mapping")
}
} }
b.StopTimer() b.StopTimer()
@ -52,7 +55,10 @@ type structName struct {
func BenchmarkMapFormName(b *testing.B) { func BenchmarkMapFormName(b *testing.B) {
var s structName var s structName
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
mapForm(&s, form) err := mapForm(&s, form)
if err != nil {
b.Fatalf("Error on a form mapping")
}
} }
b.StopTimer() b.StopTimer()

34
binding/header.go Normal file
View 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)
}

View File

@ -18,6 +18,12 @@ import (
// interface{} as a Number instead of as a float64. // interface{} as a Number instead of as a float64.
var EnableDecoderUseNumber = false var EnableDecoderUseNumber = false
// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method
// on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to
// return an error when the destination is a struct and the input contains object
// keys which do not match any non-ignored, exported fields in the destination.
var EnableDecoderDisallowUnknownFields = false
type jsonBinding struct{} type jsonBinding struct{}
func (jsonBinding) Name() string { func (jsonBinding) Name() string {
@ -40,6 +46,9 @@ func decodeJSON(r io.Reader, obj interface{}) error {
if EnableDecoderUseNumber { if EnableDecoderUseNumber {
decoder.UseNumber() decoder.UseNumber()
} }
if EnableDecoderDisallowUnknownFields {
decoder.DisallowUnknownFields()
}
if err := decoder.Decode(obj); err != nil { if err := decoder.Decode(obj); err != nil {
return err return err
} }

21
binding/json_test.go Normal file
View File

@ -0,0 +1,21 @@
// 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"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestJSONBindingBindBody(t *testing.T) {
var s struct {
Foo string `json:"foo"`
}
err := jsonBinding{}.BindBody([]byte(`{"foo": "FOO"}`), &s)
require.NoError(t, err)
assert.Equal(t, "FOO", s.Foo)
}

32
binding/msgpack_test.go Normal file
View File

@ -0,0 +1,32 @@
// 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"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ugorji/go/codec"
)
func TestMsgpackBindingBindBody(t *testing.T) {
type teststruct struct {
Foo string `msgpack:"foo"`
}
var s teststruct
err := msgpackBinding{}.BindBody(msgpackBody(t, teststruct{"FOO"}), &s)
require.NoError(t, err)
assert.Equal(t, "FOO", s.Foo)
}
func msgpackBody(t *testing.T, obj interface{}) []byte {
var bs bytes.Buffer
h := &codec.MsgpackHandle{}
err := codec.NewEncoder(&bs, h).Encode(obj)
require.NoError(t, err)
return bs.Bytes()
}

View 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
}

View 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)
}

View File

@ -6,12 +6,11 @@ package binding
import ( import (
"bytes" "bytes"
"reflect"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/go-playground/validator.v8" "gopkg.in/go-playground/validator.v9"
) )
type testInterface interface { type testInterface interface {
@ -200,15 +199,8 @@ type structCustomValidation struct {
Integer int `binding:"notone"` Integer int `binding:"notone"`
} }
// notOne is a custom validator meant to be used with `validator.v8` library. func notOne(f1 validator.FieldLevel) bool {
// The method signature for `v9` is significantly different and this function if val, ok := f1.Field().Interface().(int); ok {
// would need to be changed for tests to pass after upgrade.
// See https://github.com/gin-gonic/gin/pull/1015.
func notOne(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
if val, ok := field.Interface().(int); ok {
return val != 1 return val != 1
} }
return false return false

25
binding/xml_test.go Normal file
View File

@ -0,0 +1,25 @@
// 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"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestXMLBindingBindBody(t *testing.T) {
var s struct {
Foo string `xml:"foo"`
}
xmlBody := `<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>FOO</foo>
</root>`
err := xmlBinding{}.BindBody([]byte(xmlBody), &s)
require.NoError(t, err)
assert.Equal(t, "FOO", s.Foo)
}

21
binding/yaml_test.go Normal file
View File

@ -0,0 +1,21 @@
// 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"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestYAMLBindingBindBody(t *testing.T) {
var s struct {
Foo string `yaml:"foo"`
}
err := yamlBinding{}.BindBody([]byte("foo: FOO"), &s)
require.NoError(t, err)
assert.Equal(t, "FOO", s.Foo)
}

View File

@ -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.
@ -392,8 +393,7 @@ func (c *Context) QueryArray(key string) []string {
func (c *Context) getQueryCache() { func (c *Context) getQueryCache() {
if c.queryCache == nil { if c.queryCache == nil {
c.queryCache = make(url.Values) c.queryCache = c.Request.URL.Query()
c.queryCache, _ = url.ParseQuery(c.Request.URL.RawQuery)
} }
} }
@ -490,13 +490,8 @@ func (c *Context) PostFormMap(key string) map[string]string {
// GetPostFormMap returns a map for a given form key, plus a boolean value // GetPostFormMap returns a map for a given form key, plus a boolean value
// whether at least one value exists for the given key. // whether at least one value exists for the given key.
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
req := c.Request c.getFormCache()
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { return c.get(c.formCache, key)
if err != http.ErrNotMultipart {
debugPrint("error on parse multipart form map: %v", err)
}
}
return c.get(req.PostForm, key)
} }
// get is an internal method and returns a map which satisfy conditions. // get is an internal method and returns a map which satisfy conditions.
@ -521,7 +516,11 @@ func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
return nil, err return nil, err
} }
} }
_, fh, err := c.Request.FormFile(name) f, fh, err := c.Request.FormFile(name)
if err != nil {
return nil, err
}
f.Close()
return fh, err return fh, err
} }
@ -582,6 +581,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 +640,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)
@ -739,7 +748,7 @@ func bodyAllowedForStatus(status int) bool {
// Status sets the HTTP response code. // Status sets the HTTP response code.
func (c *Context) Status(code int) { func (c *Context) Status(code int) {
c.writermem.WriteHeader(code) c.Writer.WriteHeader(code)
} }
// Header is a intelligent shortcut for c.Writer.Header().Set(key, value). // Header is a intelligent shortcut for c.Writer.Header().Set(key, value).

View File

@ -676,7 +676,7 @@ func TestContextRenderJSONP(t *testing.T) {
c.JSONP(http.StatusCreated, H{"foo": "bar"}) c.JSONP(http.StatusCreated, H{"foo": "bar"})
assert.Equal(t, http.StatusCreated, w.Code) assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, "x({\"foo\":\"bar\"})", w.Body.String()) assert.Equal(t, "x({\"foo\":\"bar\"});", w.Body.String())
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
} }
@ -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)

View File

@ -5,7 +5,6 @@
package gin package gin
import ( import (
"bytes"
"fmt" "fmt"
"html/template" "html/template"
"runtime" "runtime"
@ -13,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.
@ -38,7 +37,7 @@ func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
func debugPrintLoadTemplate(tmpl *template.Template) { func debugPrintLoadTemplate(tmpl *template.Template) {
if IsDebugging() { if IsDebugging() {
var buf bytes.Buffer var buf strings.Builder
for _, tmpl := range tmpl.Templates() { for _, tmpl := range tmpl.Templates() {
buf.WriteString("\t- ") buf.WriteString("\t- ")
buf.WriteString(tmpl.Name()) buf.WriteString(tmpl.Name())
@ -68,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.
`) `)
} }

View File

@ -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)
} }

View File

@ -5,9 +5,9 @@
package gin package gin
import ( import (
"bytes"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"github.com/gin-gonic/gin/internal/json" "github.com/gin-gonic/gin/internal/json"
) )
@ -158,7 +158,7 @@ func (a errorMsgs) String() string {
if len(a) == 0 { if len(a) == 0 {
return "" return ""
} }
var buffer bytes.Buffer var buffer strings.Builder
for i, msg := range a { for i, msg := range a {
fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err) fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
if msg.Meta != nil { if msg.Meta != nil {

17
gin.go
View File

@ -30,7 +30,7 @@ type HandlerFunc func(*Context)
// HandlersChain defines a HandlerFunc array. // HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc type HandlersChain []HandlerFunc
// Last returns the last handler in the chain. ie. the last handler is the main own. // Last returns the last handler in the chain. ie. the last handler is the main one.
func (c HandlersChain) Last() HandlerFunc { func (c HandlersChain) Last() HandlerFunc {
if length := len(c); length > 0 { if length := len(c); length > 0 {
return c[length-1] return c[length-1]
@ -320,7 +320,10 @@ func (engine *Engine) RunUnix(file string) (err error) {
return return
} }
defer listener.Close() defer listener.Close()
os.Chmod(file, 0777) err = os.Chmod(file, 0777)
if err != nil {
return
}
err = http.Serve(listener, engine) err = http.Serve(listener, engine)
return return
} }
@ -338,6 +341,15 @@ func (engine *Engine) RunFd(fd int) (err error) {
return return
} }
defer listener.Close() defer listener.Close()
err = engine.RunListener(listener)
return
}
// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests
// through the specified net.Listener
func (engine *Engine) RunListener(listener net.Listener) (err error) {
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
defer func() { debugPrintError(err) }()
err = http.Serve(listener, engine) err = http.Serve(listener, engine)
return return
} }
@ -437,7 +449,6 @@ func serveError(c *Context, code int, defaultMessage []byte) {
return return
} }
c.writermem.WriteHeaderNow() c.writermem.WriteHeaderNow()
return
} }
func redirectTrailingSlash(c *Context) { func redirectTrailingSlash(c *Context) {

View File

@ -90,7 +90,8 @@ func TestPusher(t *testing.T) {
go func() { go func() {
router.GET("/pusher", func(c *Context) { router.GET("/pusher", func(c *Context) {
if pusher := c.Writer.Pusher(); pusher != nil { if pusher := c.Writer.Pusher(); pusher != nil {
pusher.Push("/assets/app.js", nil) err := pusher.Push("/assets/app.js", nil)
assert.NoError(t, err)
} }
c.String(http.StatusOK, "it worked") c.String(http.StatusOK, "it worked")
}) })
@ -207,6 +208,43 @@ func TestBadFileDescriptor(t *testing.T) {
assert.Error(t, router.RunFd(0)) assert.Error(t, router.RunFd(0))
} }
func TestListener(t *testing.T) {
router := New()
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
assert.NoError(t, err)
listener, err := net.ListenTCP("tcp", addr)
assert.NoError(t, err)
go func() {
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
assert.NoError(t, router.RunListener(listener))
}()
// have to wait for the goroutine to start and run the server
// otherwise the main thread will complete
time.Sleep(5 * time.Millisecond)
c, err := net.Dial("tcp", listener.Addr().String())
assert.NoError(t, err)
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
scanner := bufio.NewScanner(c)
var response string
for scanner.Scan() {
response += scanner.Text()
}
assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
assert.Contains(t, response, "it worked", "resp body should match")
}
func TestBadListener(t *testing.T) {
router := New()
addr, err := net.ResolveTCPAddr("tcp", "localhost:10086")
assert.NoError(t, err)
listener, err := net.ListenTCP("tcp", addr)
assert.NoError(t, err)
listener.Close()
assert.Error(t, router.RunListener(listener))
}
func TestWithHttptestWithAutoSelectedPort(t *testing.T) { func TestWithHttptestWithAutoSelectedPort(t *testing.T) {
router := New() router := New()
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") }) router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })

18
go.mod
View File

@ -4,15 +4,15 @@ go 1.12
require ( require (
github.com/gin-contrib/sse v0.1.0 github.com/gin-contrib/sse v0.1.0
github.com/golang/protobuf v1.3.1 github.com/go-playground/locales v0.12.1 // indirect
github.com/json-iterator/go v1.1.6 github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/mattn/go-isatty v0.0.7 github.com/golang/protobuf v1.3.2
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/json-iterator/go v1.1.7
github.com/modern-go/reflect2 v1.0.1 // indirect github.com/leodido/go-urn v1.1.0 // indirect
github.com/stretchr/testify v1.3.0 github.com/mattn/go-isatty v0.0.9
github.com/ugorji/go v1.1.4 github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c github.com/ugorji/go/codec v1.1.7
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.v9 v9.29.1
gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.2
) )

52
go.sum
View File

@ -1,36 +1,42 @@
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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 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.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -22,18 +22,19 @@ const (
forceColor forceColor
) )
var ( const (
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) green = "\033[97;42m"
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) white = "\033[90;47m"
yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) yellow = "\033[90;43m"
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) red = "\033[97;41m"
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) blue = "\033[97;44m"
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) magenta = "\033[97;45m"
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) cyan = "\033[97;46m"
reset = string([]byte{27, 91, 48, 109}) reset = "\033[0m"
consoleColorMode = autoColor
) )
var consoleColorMode = autoColor
// LoggerConfig defines the config for Logger middleware. // LoggerConfig defines the config for Logger middleware.
type LoggerConfig struct { type LoggerConfig struct {
// Optional. Default value is gin.defaultLogFormatter // Optional. Default value is gin.defaultLogFormatter

View File

@ -291,14 +291,14 @@ func TestColorForMethod(t *testing.T) {
return p.MethodColor() return p.MethodColor()
} }
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 52, 109}), colorForMethod("GET"), "get should be blue") assert.Equal(t, blue, colorForMethod("GET"), "get should be blue")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 54, 109}), colorForMethod("POST"), "post should be cyan") assert.Equal(t, cyan, colorForMethod("POST"), "post should be cyan")
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForMethod("PUT"), "put should be yellow") assert.Equal(t, yellow, colorForMethod("PUT"), "put should be yellow")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForMethod("DELETE"), "delete should be red") assert.Equal(t, red, colorForMethod("DELETE"), "delete should be red")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForMethod("PATCH"), "patch should be green") assert.Equal(t, green, colorForMethod("PATCH"), "patch should be green")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 53, 109}), colorForMethod("HEAD"), "head should be magenta") assert.Equal(t, magenta, colorForMethod("HEAD"), "head should be magenta")
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForMethod("OPTIONS"), "options should be white") assert.Equal(t, white, colorForMethod("OPTIONS"), "options should be white")
assert.Equal(t, string([]byte{27, 91, 48, 109}), colorForMethod("TRACE"), "trace is not defined and should be the reset color") assert.Equal(t, reset, colorForMethod("TRACE"), "trace is not defined and should be the reset color")
} }
func TestColorForStatus(t *testing.T) { func TestColorForStatus(t *testing.T) {
@ -309,10 +309,10 @@ func TestColorForStatus(t *testing.T) {
return p.StatusCodeColor() return p.StatusCodeColor()
} }
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 50, 109}), colorForStatus(http.StatusOK), "2xx should be green") assert.Equal(t, green, colorForStatus(http.StatusOK), "2xx should be green")
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 55, 109}), colorForStatus(http.StatusMovedPermanently), "3xx should be white") assert.Equal(t, white, colorForStatus(http.StatusMovedPermanently), "3xx should be white")
assert.Equal(t, string([]byte{27, 91, 57, 48, 59, 52, 51, 109}), colorForStatus(http.StatusNotFound), "4xx should be yellow") assert.Equal(t, yellow, colorForStatus(http.StatusNotFound), "4xx should be yellow")
assert.Equal(t, string([]byte{27, 91, 57, 55, 59, 52, 49, 109}), colorForStatus(2), "other things should be red") assert.Equal(t, red, colorForStatus(2), "other things should be red")
} }
func TestResetColor(t *testing.T) { func TestResetColor(t *testing.T) {

View File

@ -71,12 +71,18 @@ func DisableBindValidation() {
binding.Validator = nil binding.Validator = nil
} }
// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to // EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumber to
// call the UseNumber method on the JSON Decoder instance. // call the UseNumber method on the JSON Decoder instance.
func EnableJsonDecoderUseNumber() { func EnableJsonDecoderUseNumber() {
binding.EnableDecoderUseNumber = true binding.EnableDecoderUseNumber = true
} }
// EnableJsonDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to
// call the DisallowUnknownFields method on the JSON Decoder instance.
func EnableJsonDecoderDisallowUnknownFields() {
binding.EnableDecoderDisallowUnknownFields = true
}
// Mode returns currently gin mode. // Mode returns currently gin mode.
func Mode() string { func Mode() string {
return modeName return modeName

View File

@ -40,8 +40,22 @@ func TestSetMode(t *testing.T) {
assert.Panics(t, func() { SetMode("unknown") }) assert.Panics(t, func() { SetMode("unknown") })
} }
func TestDisableBindValidation(t *testing.T) {
v := binding.Validator
assert.NotNil(t, binding.Validator)
DisableBindValidation()
assert.Nil(t, binding.Validator)
binding.Validator = v
}
func TestEnableJsonDecoderUseNumber(t *testing.T) { func TestEnableJsonDecoderUseNumber(t *testing.T) {
assert.False(t, binding.EnableDecoderUseNumber) assert.False(t, binding.EnableDecoderUseNumber)
EnableJsonDecoderUseNumber() EnableJsonDecoderUseNumber()
assert.True(t, binding.EnableDecoderUseNumber) assert.True(t, binding.EnableDecoderUseNumber)
} }
func TestEnableJsonDecoderDisallowUnknownFields(t *testing.T) {
assert.False(t, binding.EnableDecoderDisallowUnknownFields)
EnableJsonDecoderDisallowUnknownFields()
assert.True(t, binding.EnableDecoderDisallowUnknownFields)
}

View File

@ -138,7 +138,7 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
if err != nil { if err != nil {
return err return err
} }
_, err = w.Write([]byte(")")) _, err = w.Write([]byte(");"))
if err != nil { if err != nil {
return err return err
} }

View File

@ -21,7 +21,9 @@ type Reader struct {
// Render (Reader) writes data with custom ContentType and headers. // Render (Reader) writes data with custom ContentType and headers.
func (r Reader) Render(w http.ResponseWriter) (err error) { func (r Reader) Render(w http.ResponseWriter) (err error) {
r.WriteContentType(w) r.WriteContentType(w)
r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) if r.ContentLength >= 0 {
r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
}
r.writeHeaders(w, r.Headers) r.writeHeaders(w, r.Headers)
_, err = io.Copy(w, r.Reader) _, err = io.Copy(w, r.Reader)
return return

View File

@ -45,7 +45,7 @@ func TestRenderMsgPack(t *testing.T) {
err = codec.NewEncoder(buf, h).Encode(data) err = codec.NewEncoder(buf, h).Encode(data)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, w.Body.String(), string(buf.Bytes())) assert.Equal(t, w.Body.String(), buf.String())
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
} }
@ -146,7 +146,7 @@ func TestRenderJsonpJSON(t *testing.T) {
err1 := (JsonpJSON{"x", data}).Render(w1) err1 := (JsonpJSON{"x", data}).Render(w1)
assert.NoError(t, err1) assert.NoError(t, err1)
assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String()) assert.Equal(t, "x({\"foo\":\"bar\"});", w1.Body.String())
assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
w2 := httptest.NewRecorder() w2 := httptest.NewRecorder()
@ -158,7 +158,7 @@ func TestRenderJsonpJSON(t *testing.T) {
err2 := (JsonpJSON{"x", datas}).Render(w2) err2 := (JsonpJSON{"x", datas}).Render(w2)
assert.NoError(t, err2) assert.NoError(t, err2)
assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String()) assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}]);", w2.Body.String())
assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type"))
} }
@ -347,7 +347,20 @@ func TestRenderRedirect(t *testing.T) {
} }
w = httptest.NewRecorder() w = httptest.NewRecorder()
assert.Panics(t, func() { assert.NoError(t, data2.Render(w)) }) assert.PanicsWithValue(t, "Cannot redirect with status code 200", func() {
err := data2.Render(w)
assert.NoError(t, err)
})
data3 := Redirect{
Code: http.StatusCreated,
Request: req,
Location: "/new/location",
}
w = httptest.NewRecorder()
err = data3.Render(w)
assert.NoError(t, err)
// only improve coverage // only improve coverage
data2.WriteContentType(w) data2.WriteContentType(w)
@ -498,3 +511,26 @@ func TestRenderReader(t *testing.T) {
assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id"))
} }
func TestRenderReaderNoContentLength(t *testing.T) {
w := httptest.NewRecorder()
body := "#!PNG some raw data"
headers := make(map[string]string)
headers["Content-Disposition"] = `attachment; filename="filename.png"`
headers["x-request-id"] = "requestId"
err := (Reader{
ContentLength: -1,
ContentType: "image/png",
Reader: strings.NewReader(body),
Headers: headers,
}).Render(w)
assert.NoError(t, err)
assert.Equal(t, body, w.Body.String())
assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
assert.NotContains(t, "Content-Length", w.Header())
assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id"))
}

View File

@ -29,38 +29,38 @@ func init() {
} }
func TestResponseWriterReset(t *testing.T) { func TestResponseWriterReset(t *testing.T) {
testWritter := httptest.NewRecorder() testWriter := httptest.NewRecorder()
writer := &responseWriter{} writer := &responseWriter{}
var w ResponseWriter = writer var w ResponseWriter = writer
writer.reset(testWritter) writer.reset(testWriter)
assert.Equal(t, -1, writer.size) assert.Equal(t, -1, writer.size)
assert.Equal(t, http.StatusOK, writer.status) assert.Equal(t, http.StatusOK, writer.status)
assert.Equal(t, testWritter, writer.ResponseWriter) assert.Equal(t, testWriter, writer.ResponseWriter)
assert.Equal(t, -1, w.Size()) assert.Equal(t, -1, w.Size())
assert.Equal(t, http.StatusOK, w.Status()) assert.Equal(t, http.StatusOK, w.Status())
assert.False(t, w.Written()) assert.False(t, w.Written())
} }
func TestResponseWriterWriteHeader(t *testing.T) { func TestResponseWriterWriteHeader(t *testing.T) {
testWritter := httptest.NewRecorder() testWriter := httptest.NewRecorder()
writer := &responseWriter{} writer := &responseWriter{}
writer.reset(testWritter) writer.reset(testWriter)
w := ResponseWriter(writer) w := ResponseWriter(writer)
w.WriteHeader(http.StatusMultipleChoices) w.WriteHeader(http.StatusMultipleChoices)
assert.False(t, w.Written()) assert.False(t, w.Written())
assert.Equal(t, http.StatusMultipleChoices, w.Status()) assert.Equal(t, http.StatusMultipleChoices, w.Status())
assert.NotEqual(t, http.StatusMultipleChoices, testWritter.Code) assert.NotEqual(t, http.StatusMultipleChoices, testWriter.Code)
w.WriteHeader(-1) w.WriteHeader(-1)
assert.Equal(t, http.StatusMultipleChoices, w.Status()) assert.Equal(t, http.StatusMultipleChoices, w.Status())
} }
func TestResponseWriterWriteHeadersNow(t *testing.T) { func TestResponseWriterWriteHeadersNow(t *testing.T) {
testWritter := httptest.NewRecorder() testWriter := httptest.NewRecorder()
writer := &responseWriter{} writer := &responseWriter{}
writer.reset(testWritter) writer.reset(testWriter)
w := ResponseWriter(writer) w := ResponseWriter(writer)
w.WriteHeader(http.StatusMultipleChoices) w.WriteHeader(http.StatusMultipleChoices)
@ -68,7 +68,7 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) {
assert.True(t, w.Written()) assert.True(t, w.Written())
assert.Equal(t, 0, w.Size()) assert.Equal(t, 0, w.Size())
assert.Equal(t, http.StatusMultipleChoices, testWritter.Code) assert.Equal(t, http.StatusMultipleChoices, testWriter.Code)
writer.size = 10 writer.size = 10
w.WriteHeaderNow() w.WriteHeaderNow()
@ -76,30 +76,30 @@ func TestResponseWriterWriteHeadersNow(t *testing.T) {
} }
func TestResponseWriterWrite(t *testing.T) { func TestResponseWriterWrite(t *testing.T) {
testWritter := httptest.NewRecorder() testWriter := httptest.NewRecorder()
writer := &responseWriter{} writer := &responseWriter{}
writer.reset(testWritter) writer.reset(testWriter)
w := ResponseWriter(writer) w := ResponseWriter(writer)
n, err := w.Write([]byte("hola")) n, err := w.Write([]byte("hola"))
assert.Equal(t, 4, n) assert.Equal(t, 4, n)
assert.Equal(t, 4, w.Size()) assert.Equal(t, 4, w.Size())
assert.Equal(t, http.StatusOK, w.Status()) assert.Equal(t, http.StatusOK, w.Status())
assert.Equal(t, http.StatusOK, testWritter.Code) assert.Equal(t, http.StatusOK, testWriter.Code)
assert.Equal(t, "hola", testWritter.Body.String()) assert.Equal(t, "hola", testWriter.Body.String())
assert.NoError(t, err) assert.NoError(t, err)
n, err = w.Write([]byte(" adios")) n, err = w.Write([]byte(" adios"))
assert.Equal(t, 6, n) assert.Equal(t, 6, n)
assert.Equal(t, 10, w.Size()) assert.Equal(t, 10, w.Size())
assert.Equal(t, "hola adios", testWritter.Body.String()) assert.Equal(t, "hola adios", testWriter.Body.String())
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestResponseWriterHijack(t *testing.T) { func TestResponseWriterHijack(t *testing.T) {
testWritter := httptest.NewRecorder() testWriter := httptest.NewRecorder()
writer := &responseWriter{} writer := &responseWriter{}
writer.reset(testWritter) writer.reset(testWriter)
w := ResponseWriter(writer) w := ResponseWriter(writer)
assert.Panics(t, func() { assert.Panics(t, func() {

View File

@ -193,13 +193,15 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
file := c.Param("filepath") file := c.Param("filepath")
// 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 { f, err := fs.Open(file)
if err != nil {
c.Writer.WriteHeader(http.StatusNotFound) c.Writer.WriteHeader(http.StatusNotFound)
c.handlers = group.engine.noRoute c.handlers = group.engine.noRoute
// Reset index // Reset index
c.index = -1 c.index = -1
return return
} }
f.Close()
fileServer.ServeHTTP(c.Writer, c.Request) fileServer.ServeHTTP(c.Writer, c.Request)
} }

View File

@ -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 {

14
tree.go
View File

@ -65,10 +65,9 @@ func min(a, b int) int {
func countParams(path string) uint8 { func countParams(path string) uint8 {
var n uint var n uint
for i := 0; i < len(path); i++ { for i := 0; i < len(path); i++ {
if path[i] != ':' && path[i] != '*' { if path[i] == ':' || path[i] == '*' {
continue n++
} }
n++
} }
if n >= 255 { if n >= 255 {
return 255 return 255
@ -128,6 +127,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 +156,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 +172,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 +180,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 +214,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 +223,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 +374,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

View File

@ -146,6 +146,6 @@ func resolveAddress(addr []string) string {
case 1: case 1:
return addr[0] return addr[0]
default: default:
panic("too much parameters") panic("too many parameters")
} }
} }

56
vendor/vendor.json vendored
View File

@ -18,6 +18,28 @@
"version": "v0.1", "version": "v0.1",
"versionExact": "v0.1.0" "versionExact": "v0.1.0"
}, },
{
"checksumSHA1": "b4DmyMT9bicTRVJw1hJXHLhIH+0=",
"path": "github.com/go-playground/locales",
"revision": "f63010822830b6fe52288ee52d5a1151088ce039",
"revisionTime": "2018-03-23T16:04:04Z",
"version": "v0.12",
"versionExact": "v0.12.1"
},
{
"checksumSHA1": "JgF260rC9YpWyY5WEljjimWLUXs=",
"path": "github.com/go-playground/locales/currency",
"revision": "630ebbb602847eba93e75ae38bbc7bb7abcf1ff3",
"revisionTime": "2019-04-30T15:33:29Z"
},
{
"checksumSHA1": "9pKcUHBaVS+360X6h4IowhmOPjk=",
"path": "github.com/go-playground/universal-translator",
"revision": "b32fa301c9fe55953584134cb6853a13c87ec0a1",
"revisionTime": "2017-02-09T16:11:52Z",
"version": "v0.16",
"versionExact": "v0.16.0"
},
{ {
"checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=", "checksumSHA1": "Y2MOwzNZfl4NRNDbLCZa6sgx7O0=",
"path": "github.com/golang/protobuf/proto", "path": "github.com/golang/protobuf/proto",
@ -26,6 +48,14 @@
"version": "v1.3", "version": "v1.3",
"versionExact": "v1.3.0" "versionExact": "v1.3.0"
}, },
{
"checksumSHA1": "zNo6yGy/bCJuzkEcP70oEBtOB2M=",
"path": "github.com/leodido/go-urn",
"revision": "70078a794e8ea4b497ba7c19a78cd60f90ccf0f4",
"revisionTime": "2018-05-24T03:26:21Z",
"version": "v1.1",
"versionExact": "v1.1.0"
},
{ {
"checksumSHA1": "TB2vxux9xQbvsTHOVt4aRTuvSn4=", "checksumSHA1": "TB2vxux9xQbvsTHOVt4aRTuvSn4=",
"path": "github.com/json-iterator/go", "path": "github.com/json-iterator/go",
@ -83,12 +113,18 @@
"versionExact": "v1.2.2" "versionExact": "v1.2.2"
}, },
{ {
"checksumSHA1": "csplo594qomjp2IZj82y7mTueOw=", "checksumSHA1": "wnEANt4k5X/KGwoFyfSSnpxULm4=",
"path": "github.com/stretchr/testify/require",
"revision": "f35b8ab0b5a2cef36673838d662e249dd9c94686",
"revisionTime": "2018-05-06T18:05:49Z"
},
{
"checksumSHA1": "S4ei9eSqVThDio0Jn2sav6yUbvg=",
"path": "github.com/ugorji/go/codec", "path": "github.com/ugorji/go/codec",
"revision": "2adff0894ba3bc2eeb9f9aea45fefd49802e1a13", "revision": "82dbfaf494e3b01d2d481376f11f6a5c8cf9599f",
"revisionTime": "2019-04-08T19:08:48Z", "revisionTime": "2019-07-02T14:15:27Z",
"version": "v1.1", "version": "v1.1",
"versionExact": "v1.1.4" "versionExact": "v1.1.6"
}, },
{ {
"checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=", "checksumSHA1": "2gaep1KNRDNyDA3O+KgPTQsGWvs=",
@ -97,12 +133,12 @@
"revisionTime": "2019-05-02T15:41:39Z" "revisionTime": "2019-05-02T15:41:39Z"
}, },
{ {
"checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", "checksumSHA1": "ACzc7AkwLtNgKhqtj8V7SGUJgnw=",
"path": "gopkg.in/go-playground/validator.v8", "path": "gopkg.in/go-playground/validator.v9",
"revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf", "revision": "46b4b1e301c24cac870ffcb4ba5c8a703d1ef475",
"revisionTime": "2017-07-30T05:02:35Z", "revisionTime": "2019-03-31T13:31:25Z",
"version": "v8.18.2", "version": "v9.28",
"versionExact": "v8.18.2" "versionExact": "v9.28.0"
}, },
{ {
"checksumSHA1": "QqDq2x8XOU7IoOR98Cx1eiV5QY8=", "checksumSHA1": "QqDq2x8XOU7IoOR98Cx1eiV5QY8=",