mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-17 05:42:09 +08:00
Merge branch 'master' into optimize-code-adjust
This commit is contained in:
commit
7e656c681d
@ -1,5 +1,11 @@
|
||||
# Gin ChangeLog
|
||||
|
||||
## Gin v1.7.2
|
||||
|
||||
### BUGFIXES
|
||||
|
||||
* Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696).
|
||||
|
||||
## Gin v1.7.1
|
||||
|
||||
### BUGFIXES
|
||||
|
@ -702,7 +702,7 @@ func main() {
|
||||
// Example for binding XML (
|
||||
// <?xml version="1.0" encoding="UTF-8"?>
|
||||
// <root>
|
||||
// <user>user</user>
|
||||
// <user>manu</user>
|
||||
// <password>123</password>
|
||||
// </root>)
|
||||
router.POST("/loginXML", func(c *gin.Context) {
|
||||
|
@ -71,7 +71,7 @@ func (v *defaultValidator) validateStruct(obj interface{}) error {
|
||||
// Engine returns the underlying validator engine which powers the default
|
||||
// Validator instance. This is useful if you want to register custom validations
|
||||
// or struct level validations. See validator GoDoc for more info -
|
||||
// https://godoc.org/gopkg.in/go-playground/validator.v8
|
||||
// https://pkg.go.dev/github.com/go-playground/validator/v10
|
||||
func (v *defaultValidator) Engine() interface{} {
|
||||
v.lazyinit()
|
||||
return v.validate
|
||||
|
@ -124,7 +124,7 @@ func createRequestMultipartFiles(t *testing.T, files ...testFile) *http.Request
|
||||
|
||||
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
|
||||
assert.Equal(t, int64(len(file.Content)), fh.Size)
|
||||
|
||||
fl, err := fh.Open()
|
||||
assert.NoError(t, err)
|
||||
|
13
context.go
13
context.go
@ -86,17 +86,17 @@ type Context struct {
|
||||
|
||||
func (c *Context) reset() {
|
||||
c.Writer = &c.writermem
|
||||
c.Params = c.Params[0:0]
|
||||
c.Params = c.Params[:0]
|
||||
c.handlers = nil
|
||||
c.index = -1
|
||||
|
||||
c.fullPath = ""
|
||||
c.Keys = nil
|
||||
c.Errors = c.Errors[0:0]
|
||||
c.Errors = c.Errors[:0]
|
||||
c.Accepted = nil
|
||||
c.queryCache = nil
|
||||
c.formCache = nil
|
||||
*c.params = (*c.params)[0:0]
|
||||
*c.params = (*c.params)[:0]
|
||||
}
|
||||
|
||||
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
||||
@ -731,10 +731,15 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e
|
||||
// If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy,
|
||||
// the remote IP (coming form Request.RemoteAddr) is returned.
|
||||
func (c *Context) ClientIP() string {
|
||||
if c.engine.AppEngine {
|
||||
switch {
|
||||
case c.engine.AppEngine:
|
||||
if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
|
||||
return addr
|
||||
}
|
||||
case c.engine.CloudflareProxy:
|
||||
if addr := c.requestHeader("CF-Connecting-IP"); addr != "" {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
|
||||
remoteIP, trusted := c.RemoteIP()
|
||||
|
@ -1392,14 +1392,10 @@ func TestContextAbortWithError(t *testing.T) {
|
||||
assert.True(t, c.IsAborted())
|
||||
}
|
||||
|
||||
func resetTrustedCIDRs(c *Context) {
|
||||
c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs()
|
||||
}
|
||||
|
||||
func TestContextClientIP(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
resetTrustedCIDRs(c)
|
||||
c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs()
|
||||
resetContextForClientIPTests(c)
|
||||
|
||||
// Legacy tests (validating that the defaults don't break the
|
||||
@ -1428,57 +1424,47 @@ func TestContextClientIP(t *testing.T) {
|
||||
resetContextForClientIPTests(c)
|
||||
|
||||
// No trusted proxies
|
||||
c.engine.TrustedProxies = []string{}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{})
|
||||
c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"}
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// Last proxy is trusted, but the RemoteAddr is not
|
||||
c.engine.TrustedProxies = []string{"30.30.30.30"}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{"30.30.30.30"})
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// Only trust RemoteAddr
|
||||
c.engine.TrustedProxies = []string{"40.40.40.40"}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
|
||||
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
||||
|
||||
// All steps are trusted
|
||||
c.engine.TrustedProxies = []string{"40.40.40.40", "30.30.30.30", "20.20.20.20"}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40", "30.30.30.30", "20.20.20.20"})
|
||||
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
||||
|
||||
// Use CIDR
|
||||
c.engine.TrustedProxies = []string{"40.40.25.25/16", "30.30.30.30"}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{"40.40.25.25/16", "30.30.30.30"})
|
||||
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
||||
|
||||
// Use hostname that resolves to all the proxies
|
||||
c.engine.TrustedProxies = []string{"foo"}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{"foo"})
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// Use hostname that returns an error
|
||||
c.engine.TrustedProxies = []string{"bar"}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{"bar"})
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// X-Forwarded-For has a non-IP element
|
||||
c.engine.TrustedProxies = []string{"40.40.40.40"}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
|
||||
c.Request.Header.Set("X-Forwarded-For", " blah ")
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// Result from LookupHost has non-IP element. This should never
|
||||
// happen, but we should test it to make sure we handle it
|
||||
// gracefully.
|
||||
c.engine.TrustedProxies = []string{"baz"}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{"baz"})
|
||||
c.Request.Header.Set("X-Forwarded-For", " 30.30.30.30 ")
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
c.engine.TrustedProxies = []string{"40.40.40.40"}
|
||||
resetTrustedCIDRs(c)
|
||||
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
|
||||
c.Request.Header.Del("X-Forwarded-For")
|
||||
c.engine.RemoteIPHeaders = []string{"X-Forwarded-For", "X-Real-IP"}
|
||||
assert.Equal(t, "10.10.10.10", c.ClientIP())
|
||||
@ -1490,6 +1476,13 @@ func TestContextClientIP(t *testing.T) {
|
||||
c.Request.Header.Del("X-Appengine-Remote-Addr")
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
c.engine.AppEngine = false
|
||||
c.engine.CloudflareProxy = true
|
||||
assert.Equal(t, "60.60.60.60", c.ClientIP())
|
||||
|
||||
c.Request.Header.Del("CF-Connecting-IP")
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// no port
|
||||
c.Request.RemoteAddr = "50.50.50.50"
|
||||
assert.Empty(t, c.ClientIP())
|
||||
@ -1499,6 +1492,7 @@ func resetContextForClientIPTests(c *Context) {
|
||||
c.Request.Header.Set("X-Real-IP", " 10.10.10.10 ")
|
||||
c.Request.Header.Set("X-Forwarded-For", " 20.20.20.20, 30.30.30.30")
|
||||
c.Request.Header.Set("X-Appengine-Remote-Addr", "50.50.50.50")
|
||||
c.Request.Header.Set("CF-Connecting-IP", "60.60.60.60")
|
||||
c.Request.RemoteAddr = " 40.40.40.40:42123 "
|
||||
c.engine.AppEngine = false
|
||||
}
|
||||
|
2
fs.go
2
fs.go
@ -17,7 +17,7 @@ type neuteredReaddirFile struct {
|
||||
http.File
|
||||
}
|
||||
|
||||
// Dir returns a http.Filesystem that can be used by http.FileServer(). It is used internally
|
||||
// Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally
|
||||
// in router.Static().
|
||||
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
|
||||
// a filesystem that prevents http.FileServer() to list the directory files.
|
||||
|
42
gin.go
42
gin.go
@ -105,6 +105,10 @@ type Engine struct {
|
||||
// 'X-AppEngine...' for better integration with that PaaS.
|
||||
AppEngine bool
|
||||
|
||||
// If enabled, it will trust the CF-Connecting-IP header to determine the
|
||||
// IP of the client.
|
||||
CloudflareProxy bool
|
||||
|
||||
// If enabled, the url.RawPath will be used to find parameters.
|
||||
UseRawPath bool
|
||||
|
||||
@ -326,11 +330,11 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
||||
func (engine *Engine) Run(addr ...string) (err error) {
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
trustedCIDRs, err := engine.prepareTrustedCIDRs()
|
||||
err = engine.parseTrustedProxies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
engine.trustedCIDRs = trustedCIDRs
|
||||
|
||||
address := resolveAddress(addr)
|
||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||
err = http.ListenAndServe(address, engine)
|
||||
@ -366,6 +370,19 @@ func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
||||
return cidr, nil
|
||||
}
|
||||
|
||||
// SetTrustedProxies set Engine.TrustedProxies
|
||||
func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
|
||||
engine.TrustedProxies = trustedProxies
|
||||
return engine.parseTrustedProxies()
|
||||
}
|
||||
|
||||
// parseTrustedProxies parse Engine.TrustedProxies to Engine.trustedCIDRs
|
||||
func (engine *Engine) parseTrustedProxies() error {
|
||||
trustedCIDRs, err := engine.prepareTrustedCIDRs()
|
||||
engine.trustedCIDRs = trustedCIDRs
|
||||
return err
|
||||
}
|
||||
|
||||
// parseIP parse a string representation of an IP and returns a net.IP with the
|
||||
// minimum byte representation or nil if input is invalid.
|
||||
func parseIP(ip string) net.IP {
|
||||
@ -387,6 +404,11 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
|
||||
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
err = engine.parseTrustedProxies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
|
||||
return
|
||||
}
|
||||
@ -398,6 +420,11 @@ func (engine *Engine) RunUnix(file string) (err error) {
|
||||
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
err = engine.parseTrustedProxies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unix", file)
|
||||
if err != nil {
|
||||
return
|
||||
@ -416,6 +443,11 @@ func (engine *Engine) RunFd(fd int) (err error) {
|
||||
debugPrint("Listening and serving HTTP on fd@%d", fd)
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
err = engine.parseTrustedProxies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
|
||||
listener, err := net.FileListener(f)
|
||||
if err != nil {
|
||||
@ -431,6 +463,12 @@ func (engine *Engine) RunFd(fd int) (err error) {
|
||||
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 = engine.parseTrustedProxies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = http.Serve(listener, engine)
|
||||
return
|
||||
}
|
||||
|
@ -55,13 +55,74 @@ func TestRunEmpty(t *testing.T) {
|
||||
testRequest(t, "http://localhost:8080/example")
|
||||
}
|
||||
|
||||
func TestTrustedCIDRsForRun(t *testing.T) {
|
||||
func TestBadTrustedCIDRsForRun(t *testing.T) {
|
||||
os.Setenv("PORT", "")
|
||||
router := New()
|
||||
router.TrustedProxies = []string{"hello/world"}
|
||||
assert.Error(t, router.Run(":8080"))
|
||||
}
|
||||
|
||||
func TestBadTrustedCIDRsForRunUnix(t *testing.T) {
|
||||
router := New()
|
||||
router.TrustedProxies = []string{"hello/world"}
|
||||
|
||||
unixTestSocket := filepath.Join(os.TempDir(), "unix_unit_test")
|
||||
|
||||
defer os.Remove(unixTestSocket)
|
||||
|
||||
go func() {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
assert.Error(t, router.RunUnix(unixTestSocket))
|
||||
}()
|
||||
// have to wait for the goroutine to start and run the server
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestBadTrustedCIDRsForRunFd(t *testing.T) {
|
||||
router := New()
|
||||
router.TrustedProxies = []string{"hello/world"}
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||
assert.NoError(t, err)
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
assert.NoError(t, err)
|
||||
socketFile, err := listener.File()
|
||||
assert.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
assert.Error(t, router.RunFd(int(socketFile.Fd())))
|
||||
}()
|
||||
// have to wait for the goroutine to start and run the server
|
||||
// otherwise the main thread will complete
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestBadTrustedCIDRsForRunListener(t *testing.T) {
|
||||
router := New()
|
||||
router.TrustedProxies = []string{"hello/world"}
|
||||
|
||||
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.Error(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)
|
||||
}
|
||||
|
||||
func TestBadTrustedCIDRsForRunTLS(t *testing.T) {
|
||||
os.Setenv("PORT", "")
|
||||
router := New()
|
||||
router.TrustedProxies = []string{"hello/world"}
|
||||
assert.Error(t, router.RunTLS(":8080", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||
}
|
||||
|
||||
func TestRunTLS(t *testing.T) {
|
||||
router := New()
|
||||
go func() {
|
||||
|
5
go.mod
5
go.mod
@ -4,11 +4,12 @@ go 1.13
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/sse v0.1.0
|
||||
github.com/go-playground/validator/v10 v10.4.1
|
||||
github.com/go-playground/validator/v10 v10.6.1
|
||||
github.com/goccy/go-json v0.5.1
|
||||
github.com/golang/protobuf v1.3.3
|
||||
github.com/json-iterator/go v1.1.9
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/ugorji/go/codec v1.1.7
|
||||
github.com/ugorji/go/codec v1.2.6
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
|
17
go.sum
17
go.sum
@ -9,10 +9,10 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/goccy/go-json v0.4.11 h1:92nyX606ZN/cUFwctfxwDWm8YWSA38Zlv9s7taFeLyo=
|
||||
github.com/goccy/go-json v0.4.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I=
|
||||
github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
||||
github.com/goccy/go-json v0.5.1 h1:R9UYTOUvo7eIY9aeDMZ4L6OVtHaSr1k2No9W6MKjXrA=
|
||||
github.com/goccy/go-json v0.5.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@ -32,10 +32,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
|
||||
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
|
||||
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
|
||||
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@ -45,6 +45,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
|
29
tree.go
29
tree.go
@ -118,6 +118,11 @@ type node struct {
|
||||
fullPath string
|
||||
}
|
||||
|
||||
type skip struct {
|
||||
path string
|
||||
paramNode *node
|
||||
}
|
||||
|
||||
// Increments priority of the given child and reorders if necessary
|
||||
func (n *node) incrementChildPrio(pos int) int {
|
||||
cs := n.children
|
||||
@ -400,6 +405,8 @@ type nodeValue struct {
|
||||
// made if a handle exists with an extra (without the) trailing slash for the
|
||||
// given path.
|
||||
func (n *node) getValue(path string, params *Params, unescape bool) (value nodeValue) {
|
||||
var skipped *skip
|
||||
|
||||
walk: // Outer loop for walking the tree
|
||||
for {
|
||||
prefix := n.path
|
||||
@ -411,6 +418,21 @@ walk: // Outer loop for walking the tree
|
||||
idxc := path[0]
|
||||
for i, c := range []byte(n.indices) {
|
||||
if c == idxc {
|
||||
if strings.HasPrefix(n.children[len(n.children)-1].path, ":") {
|
||||
skipped = &skip{
|
||||
path: prefix + path,
|
||||
paramNode: &node{
|
||||
path: n.path,
|
||||
wildChild: n.wildChild,
|
||||
nType: n.nType,
|
||||
priority: n.priority,
|
||||
children: n.children,
|
||||
handlers: n.handlers,
|
||||
fullPath: n.fullPath,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
n = n.children[i]
|
||||
continue walk
|
||||
}
|
||||
@ -542,6 +564,13 @@ walk: // Outer loop for walking the tree
|
||||
return
|
||||
}
|
||||
|
||||
if path != "/" && skipped != nil && strings.HasSuffix(skipped.path, path) {
|
||||
path = skipped.path
|
||||
n = skipped.paramNode
|
||||
skipped = nil
|
||||
continue walk
|
||||
}
|
||||
|
||||
// Nothing found. We can recommend to redirect to the same URL with an
|
||||
// extra trailing slash if a leaf exists for that path
|
||||
value.tsr = (path == "/") ||
|
||||
|
18
tree_test.go
18
tree_test.go
@ -135,13 +135,16 @@ func TestTreeWildcard(t *testing.T) {
|
||||
|
||||
routes := [...]string{
|
||||
"/",
|
||||
"/cmd/:tool/:sub",
|
||||
"/cmd/:tool/",
|
||||
"/cmd/:tool/:sub",
|
||||
"/cmd/whoami",
|
||||
"/cmd/whoami/root",
|
||||
"/cmd/whoami/root/",
|
||||
"/src/*filepath",
|
||||
"/search/",
|
||||
"/search/:query",
|
||||
"/search/gin-gonic",
|
||||
"/search/google",
|
||||
"/user_:name",
|
||||
"/user_:name/about",
|
||||
"/files/:dir/*filepath",
|
||||
@ -150,6 +153,7 @@ func TestTreeWildcard(t *testing.T) {
|
||||
"/doc/go1.html",
|
||||
"/info/:user/public",
|
||||
"/info/:user/project/:project",
|
||||
"/info/:user/project/golang",
|
||||
}
|
||||
for _, route := range routes {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
@ -159,21 +163,29 @@ func TestTreeWildcard(t *testing.T) {
|
||||
{"/", false, "/", nil},
|
||||
{"/cmd/test", true, "/cmd/:tool/", Params{Param{"tool", "test"}}},
|
||||
{"/cmd/test/", false, "/cmd/:tool/", Params{Param{"tool", "test"}}},
|
||||
{"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}},
|
||||
{"/cmd/who", true, "/cmd/:tool/", Params{Param{"tool", "who"}}},
|
||||
{"/cmd/who/", false, "/cmd/:tool/", Params{Param{"tool", "who"}}},
|
||||
{"/cmd/whoami", false, "/cmd/whoami", nil},
|
||||
{"/cmd/whoami/", true, "/cmd/whoami", nil},
|
||||
{"/cmd/whoami/r", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}},
|
||||
{"/cmd/whoami/r/", true, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "whoami"}, Param{Key: "sub", Value: "r"}}},
|
||||
{"/cmd/whoami/root", false, "/cmd/whoami/root", nil},
|
||||
{"/cmd/whoami/root/", false, "/cmd/whoami/root/", nil},
|
||||
{"/cmd/whoami/root", true, "/cmd/whoami/root/", nil},
|
||||
{"/cmd/test/3", false, "/cmd/:tool/:sub", Params{Param{Key: "tool", Value: "test"}, Param{Key: "sub", Value: "3"}}},
|
||||
{"/src/", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/"}}},
|
||||
{"/src/some/file.png", false, "/src/*filepath", Params{Param{Key: "filepath", Value: "/some/file.png"}}},
|
||||
{"/search/", false, "/search/", nil},
|
||||
{"/search/someth!ng+in+ünìcodé", false, "/search/:query", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
|
||||
{"/search/someth!ng+in+ünìcodé/", true, "", Params{Param{Key: "query", Value: "someth!ng+in+ünìcodé"}}},
|
||||
{"/search/gin", false, "/search/:query", Params{Param{"query", "gin"}}},
|
||||
{"/search/gin-gonic", false, "/search/gin-gonic", nil},
|
||||
{"/search/google", false, "/search/google", nil},
|
||||
{"/user_gopher", false, "/user_:name", Params{Param{Key: "name", Value: "gopher"}}},
|
||||
{"/user_gopher/about", false, "/user_:name/about", Params{Param{Key: "name", Value: "gopher"}}},
|
||||
{"/files/js/inc/framework.js", false, "/files/:dir/*filepath", Params{Param{Key: "dir", Value: "js"}, Param{Key: "filepath", Value: "/inc/framework.js"}}},
|
||||
{"/info/gordon/public", false, "/info/:user/public", Params{Param{Key: "user", Value: "gordon"}}},
|
||||
{"/info/gordon/project/go", false, "/info/:user/project/:project", Params{Param{Key: "user", Value: "gordon"}, Param{Key: "project", Value: "go"}}},
|
||||
{"/info/gordon/project/golang", false, "/info/:user/project/golang", Params{Param{Key: "user", Value: "gordon"}}},
|
||||
})
|
||||
|
||||
checkPriorities(t, tree)
|
||||
|
@ -5,4 +5,4 @@
|
||||
package gin
|
||||
|
||||
// Version is the current gin framework's version.
|
||||
const Version = "v1.7.1"
|
||||
const Version = "v1.7.2"
|
||||
|
Loading…
x
Reference in New Issue
Block a user