mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-17 05:42:09 +08:00
Merge branch 'master' of https://github.com/gin-gonic/gin
This commit is contained in:
commit
e3f19a5aa6
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,7 +1,7 @@
|
||||
- With pull requests:
|
||||
- Open your pull request against `master`
|
||||
- Your pull request should have no more than two commits, if not you should squash them.
|
||||
- It should pass all tests in the available continuous integration systems such as TravisCI.
|
||||
- It should pass all tests in the available continuous integration systems such as GitHub Actions.
|
||||
- You should add/modify tests to cover your proposed code changes.
|
||||
- If your pull request contains a new feature, please document it on the README.
|
||||
|
||||
|
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
84
.github/workflows/gin.yml
vendored
Normal file
84
.github/workflows/gin.yml
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
name: Run Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
version: v1.42.1
|
||||
args: --verbose
|
||||
test:
|
||||
needs: lint
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
go: [1.13, 1.14, 1.15, 1.16, 1.17]
|
||||
test-tags: ['', nomsgpack]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
go-build: ~/.cache/go-build
|
||||
- os: macos-latest
|
||||
go-build: ~/Library/Caches/go-build
|
||||
name: ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
GO111MODULE: on
|
||||
TESTTAGS: ${{ matrix.test-tags }}
|
||||
GOPROXY: https://proxy.golang.org
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go }}
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
${{ matrix.go-build }}
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Run Tests
|
||||
run: make test
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
|
||||
notification-gitter:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notification failure message
|
||||
if: failure()
|
||||
run: |
|
||||
PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)"
|
||||
curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" -d level=error https://webhooks.gitter.im/e/7f95bf605c4d356372f4
|
||||
- name: Notification success message
|
||||
if: success()
|
||||
run: |
|
||||
PR_OR_COMPARE="$(if [ "${{ github.event.pull_request }}" != "" ]; then echo "${{ github.event.pull_request.html_url }}"; else echo "${{ github.event.compare }}"; fi)"
|
||||
curl -d message="GitHub Actions [$GITHUB_REPOSITORY]($PR_OR_COMPARE) ($GITHUB_REF) [normal]($GITHUB_API_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) ($GITHUB_RUN_NUMBER)" https://webhooks.gitter.im/e/7f95bf605c4d356372f4
|
39
.golangci.yml
Normal file
39
.golangci.yml
Normal file
@ -0,0 +1,39 @@
|
||||
run:
|
||||
timeout: 5m
|
||||
linters:
|
||||
enable:
|
||||
- asciicheck
|
||||
- depguard
|
||||
- dogsled
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exportloopref
|
||||
- gci
|
||||
- gofmt
|
||||
- goimports
|
||||
- gosec
|
||||
- misspell
|
||||
- nakedret
|
||||
- nilerr
|
||||
- nolintlint
|
||||
- revive
|
||||
- wastedassign
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- structcheck
|
||||
- unused
|
||||
text: "`data` is unused"
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA1019:"
|
||||
- linters:
|
||||
- revive
|
||||
text: "var-naming:"
|
||||
- linters:
|
||||
- revive
|
||||
text: "exported:"
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gosec # security is not make sense in tests
|
52
.travis.yml
52
.travis.yml
@ -1,52 +0,0 @@
|
||||
language: go
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- go: 1.13.x
|
||||
- go: 1.13.x
|
||||
env:
|
||||
- TESTTAGS=nomsgpack
|
||||
- go: 1.14.x
|
||||
- go: 1.14.x
|
||||
env:
|
||||
- TESTTAGS=nomsgpack
|
||||
- go: 1.15.x
|
||||
- go: 1.15.x
|
||||
env:
|
||||
- TESTTAGS=nomsgpack
|
||||
- go: 1.16.x
|
||||
- go: 1.16.x
|
||||
env:
|
||||
- TESTTAGS=nomsgpack
|
||||
- go: master
|
||||
|
||||
git:
|
||||
depth: 10
|
||||
|
||||
before_install:
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi
|
||||
|
||||
install:
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi
|
||||
|
||||
go_import_path: github.com/gin-gonic/gin
|
||||
|
||||
script:
|
||||
- make vet
|
||||
- make fmt-check
|
||||
- make misspell-check
|
||||
- make test
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/7f95bf605c4d356372f4
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
@ -1,5 +1,11 @@
|
||||
# Gin ChangeLog
|
||||
|
||||
## Gin v1.7.3
|
||||
|
||||
### BUGFIXES
|
||||
|
||||
* fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796)
|
||||
|
||||
## Gin v1.7.2
|
||||
|
||||
### BUGFIXES
|
||||
|
@ -8,6 +8,6 @@
|
||||
- With pull requests:
|
||||
- Open your pull request against `master`
|
||||
- Your pull request should have no more than two commits, if not you should squash them.
|
||||
- It should pass all tests in the available continuous integration systems such as TravisCI.
|
||||
- It should pass all tests in the available continuous integration systems such as GitHub Actions.
|
||||
- You should add/modify tests to cover your proposed code changes.
|
||||
- If your pull request contains a new feature, please document it on the README.
|
||||
|
10
Makefile
10
Makefile
@ -1,5 +1,6 @@
|
||||
GO ?= go
|
||||
GOFMT ?= gofmt "-s"
|
||||
GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
PACKAGES ?= $(shell $(GO) list ./...)
|
||||
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
|
||||
GOFILES := $(shell find . -name "*.go")
|
||||
@ -67,5 +68,10 @@ misspell:
|
||||
|
||||
.PHONY: tools
|
||||
tools:
|
||||
go install golang.org/x/lint/golint; \
|
||||
go install github.com/client9/misspell/cmd/misspell;
|
||||
@if [ $(GO_VERSION) -gt 15 ]; then \
|
||||
$(GO) install golang.org/x/lint/golint@latest; \
|
||||
$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
|
||||
elif [ $(GO_VERSION) -lt 16 ]; then \
|
||||
$(GO) install golang.org/x/lint/golint; \
|
||||
$(GO) install github.com/client9/misspell/cmd/misspell; \
|
||||
fi
|
||||
|
107
README.md
107
README.md
@ -2,7 +2,7 @@
|
||||
|
||||
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
|
||||
|
||||
[](https://travis-ci.org/gin-gonic/gin)
|
||||
[](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster)
|
||||
[](https://codecov.io/gh/gin-gonic/gin)
|
||||
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||
[](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
|
||||
@ -78,6 +78,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
- [http2 server push](#http2-server-push)
|
||||
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
||||
- [Set and get a cookie](#set-and-get-a-cookie)
|
||||
- [Don't trust all proxies](#don't-trust-all-proxies)
|
||||
- [Testing](#testing)
|
||||
- [Users](#users)
|
||||
|
||||
@ -256,14 +257,15 @@ func main() {
|
||||
|
||||
// For each matched request Context will hold the route definition
|
||||
router.POST("/user/:name/*action", func(c *gin.Context) {
|
||||
c.FullPath() == "/user/:name/*action" // true
|
||||
b := c.FullPath() == "/user/:name/*action" // true
|
||||
c.String(http.StatusOK, "%t", b)
|
||||
})
|
||||
|
||||
// This handler will add a new router for /user/groups.
|
||||
// Exact routes are resolved before param routes, regardless of the order they were defined.
|
||||
// Routes starting with /user/groups are never interpreted as /user/:name/... routes
|
||||
router.GET("/user/groups", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "The available groups are [...]", name)
|
||||
c.String(http.StatusOK, "The available groups are [...]")
|
||||
})
|
||||
|
||||
router.Run(":8080")
|
||||
@ -1835,7 +1837,7 @@ func main() {
|
||||
quit := make(chan os.Signal)
|
||||
// kill (no param) default send syscall.SIGTERM
|
||||
// kill -2 is syscall.SIGINT
|
||||
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
|
||||
// kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
log.Println("Shutting down server...")
|
||||
@ -2008,7 +2010,7 @@ func SomeHandler(c *gin.Context) {
|
||||
objA := formA{}
|
||||
objB := formB{}
|
||||
// This reads c.Request.Body and stores the result into the context.
|
||||
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
|
||||
if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil {
|
||||
c.String(http.StatusOK, `the body should be formA`)
|
||||
// At this time, it reuses body stored in the context.
|
||||
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
|
||||
@ -2030,6 +2032,61 @@ enough to call binding at once.
|
||||
can be called by `c.ShouldBind()` multiple times without any damage to
|
||||
performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
|
||||
|
||||
### Bind form-data request with custom struct and custom tag
|
||||
|
||||
```go
|
||||
const (
|
||||
customerTag = "url"
|
||||
defaultMemory = 32 << 20
|
||||
)
|
||||
|
||||
type customerBinding struct {}
|
||||
|
||||
func (customerBinding) Name() string {
|
||||
return "form"
|
||||
}
|
||||
|
||||
func (customerBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
||||
if err != http.ErrNotMultipart {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {
|
||||
return err
|
||||
}
|
||||
return validate(obj)
|
||||
}
|
||||
|
||||
func validate(obj interface{}) error {
|
||||
if binding.Validator == nil {
|
||||
return nil
|
||||
}
|
||||
return binding.Validator.ValidateStruct(obj)
|
||||
}
|
||||
|
||||
// Now we can do this!!!
|
||||
// FormA is a external type that we can't modify it's tag
|
||||
type FormA struct {
|
||||
FieldA string `url:"field_a"`
|
||||
}
|
||||
|
||||
func ListHandler(s *Service) func(ctx *gin.Context) {
|
||||
return func(ctx *gin.Context) {
|
||||
var urlBinding = customerBinding{}
|
||||
var opt FormA
|
||||
err := ctx.MustBindWith(&opt, urlBinding)
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### http2 server push
|
||||
|
||||
http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information.
|
||||
@ -2155,11 +2212,17 @@ Gin lets you specify which headers to hold the real client IP (if any),
|
||||
as well as specifying which proxies (or direct clients) you trust to
|
||||
specify one of these headers.
|
||||
|
||||
The `TrustedProxies` slice on your `gin.Engine` specifes network addresses or
|
||||
network CIDRs from where clients which their request headers related to client
|
||||
Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses
|
||||
or network CIDRs from where clients which their request headers related to client
|
||||
IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
|
||||
IPv6 CIDRs.
|
||||
|
||||
**Attention:** Gin trust all proxies by default if you don't specify a trusted
|
||||
proxy using the function above, **this is NOT safe**. At the same time, if you don't
|
||||
use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
|
||||
then `Context.ClientIP()` will return the remote address directly to avoid some
|
||||
unnecessary computation.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
@ -2170,7 +2233,7 @@ import (
|
||||
func main() {
|
||||
|
||||
router := gin.Default()
|
||||
router.TrustedProxies = []string{"192.168.1.2"}
|
||||
router.SetTrustedProxies([]string{"192.168.1.2"})
|
||||
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
// If the client is 192.168.1.2, use the X-Forwarded-For
|
||||
@ -2183,6 +2246,34 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform`
|
||||
to skip TrustedProxies check, it has a higher priority than TrustedProxies.
|
||||
Look at the example below:
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
router := gin.Default()
|
||||
// Use predefined header gin.PlatformXXX
|
||||
router.TrustedPlatform = gin.PlatformGoogleAppEngine
|
||||
// Or set your own trusted request header for another trusted proxy service
|
||||
// Don't set it to any suspect request header, it's unsafe
|
||||
router.TrustedPlatform = "X-CDN-IP"
|
||||
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
// If you set TrustedPlatform, ClientIP() will resolve the
|
||||
// corresponding header and return IP directly
|
||||
fmt.Printf("ClientIP: %s\n", c.ClientIP())
|
||||
})
|
||||
router.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The `net/http/httptest` package is preferable way for HTTP testing.
|
||||
|
@ -49,7 +49,7 @@ type BindingUri interface {
|
||||
// StructValidator is the minimal interface which needs to be implemented in
|
||||
// order for it to be used as the validator engine for ensuring the correctness
|
||||
// of the request. Gin provides a default implementation for this using
|
||||
// https://github.com/go-playground/validator/tree/v8.18.2.
|
||||
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||
type StructValidator interface {
|
||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||
// If the received type is a slice|array, the validation should be performed travel on every element.
|
||||
@ -65,7 +65,7 @@ type StructValidator interface {
|
||||
}
|
||||
|
||||
// Validator is the default validator which implements the StructValidator
|
||||
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
|
||||
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
||||
// under the hood.
|
||||
var Validator StructValidator = &defaultValidator{}
|
||||
|
||||
|
@ -47,7 +47,7 @@ type BindingUri interface {
|
||||
// StructValidator is the minimal interface which needs to be implemented in
|
||||
// order for it to be used as the validator engine for ensuring the correctness
|
||||
// of the request. Gin provides a default implementation for this using
|
||||
// https://github.com/go-playground/validator/tree/v8.18.2.
|
||||
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||
type StructValidator interface {
|
||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
||||
@ -62,7 +62,7 @@ type StructValidator interface {
|
||||
}
|
||||
|
||||
// Validator is the default validator which implements the StructValidator
|
||||
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
|
||||
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
||||
// under the hood.
|
||||
var Validator StructValidator = &defaultValidator{}
|
||||
|
||||
|
@ -20,8 +20,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin/testdata/protoexample"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type appkey struct {
|
||||
@ -848,7 +848,6 @@ func testFormBindingEmbeddedStruct(t *testing.T, method, path, badPath, body, ba
|
||||
assert.Equal(t, 1, obj.Page)
|
||||
assert.Equal(t, 2, obj.Size)
|
||||
assert.Equal(t, "test-appkey", obj.Appkey)
|
||||
|
||||
}
|
||||
|
||||
func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) {
|
||||
@ -1374,6 +1373,13 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
|
||||
err := b.Bind(req, &obj)
|
||||
assert.Error(t, err)
|
||||
|
||||
invalid_obj := FooStruct{}
|
||||
req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`))
|
||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||
err = b.Bind(req, &invalid_obj)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, err.Error(), "obj is not ProtoMessage")
|
||||
|
||||
obj = protoexample.Test{}
|
||||
req = requestWithBody("POST", badPath, badBody)
|
||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||
|
@ -20,15 +20,27 @@ type defaultValidator struct {
|
||||
|
||||
type sliceValidateError []error
|
||||
|
||||
// Error concatenates all error elements in sliceValidateError into a single string separated by \n.
|
||||
func (err sliceValidateError) Error() string {
|
||||
var errMsgs []string
|
||||
for i, e := range err {
|
||||
if e == nil {
|
||||
continue
|
||||
n := len(err)
|
||||
switch n {
|
||||
case 0:
|
||||
return ""
|
||||
default:
|
||||
var b strings.Builder
|
||||
if err[0] != nil {
|
||||
fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error())
|
||||
}
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("[%d]: %s", i, e.Error()))
|
||||
if n > 1 {
|
||||
for i := 1; i < n; i++ {
|
||||
if err[i] != nil {
|
||||
b.WriteString("\n")
|
||||
fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
return strings.Join(errMsgs, "\n")
|
||||
}
|
||||
|
||||
var _ StructValidator = &defaultValidator{}
|
||||
|
20
binding/default_validator_benchmark_test.go
Normal file
20
binding/default_validator_benchmark_test.go
Normal file
@ -0,0 +1,20 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkSliceValidateError(b *testing.B) {
|
||||
const size int = 100
|
||||
for i := 0; i < b.N; i++ {
|
||||
e := make(sliceValidateError, size)
|
||||
for j := 0; j < size; j++ {
|
||||
e[j] = errors.New(strconv.Itoa(j))
|
||||
}
|
||||
if len(e.Error()) == 0 {
|
||||
b.Errorf("error")
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,26 @@ func TestSliceValidateError(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{"has nil elements", sliceValidateError{errors.New("test error"), nil}, "[0]: test error"},
|
||||
{"has zero elements", sliceValidateError{}, ""},
|
||||
{"has one element", sliceValidateError{errors.New("test one error")}, "[0]: test one error"},
|
||||
{"has two elements",
|
||||
sliceValidateError{
|
||||
errors.New("first error"),
|
||||
errors.New("second error"),
|
||||
},
|
||||
"[0]: first error\n[1]: second error",
|
||||
},
|
||||
{"has many elements",
|
||||
sliceValidateError{
|
||||
errors.New("first error"),
|
||||
errors.New("second error"),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
errors.New("last error"),
|
||||
},
|
||||
"[0]: first error\n[1]: second error\n[5]: last error",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -5,6 +5,7 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -22,7 +23,7 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
if err := req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := req.ParseMultipartForm(defaultMemory); err != nil && err != http.ErrNotMultipart {
|
||||
if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
|
||||
return err
|
||||
}
|
||||
if err := mapForm(obj, req.Form); err != nil {
|
||||
|
@ -26,7 +26,7 @@ var (
|
||||
ErrConvertToMapString = errors.New("can not convert to map of strings")
|
||||
)
|
||||
|
||||
func mapUri(ptr interface{}, m map[string][]string) error {
|
||||
func mapURI(ptr interface{}, m map[string][]string) error {
|
||||
return mapFormByTag(ptr, m, "uri")
|
||||
}
|
||||
|
||||
@ -34,6 +34,10 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
||||
return mapFormByTag(ptr, form, "form")
|
||||
}
|
||||
|
||||
func MapFormWithTag(ptr interface{}, form map[string][]string, tag string) error {
|
||||
return mapFormByTag(ptr, form, tag)
|
||||
}
|
||||
|
||||
var emptyField = reflect.StructField{}
|
||||
|
||||
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
||||
@ -57,7 +61,7 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
||||
|
||||
// setter tries to set value on a walking by fields of a struct
|
||||
type setter interface {
|
||||
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
|
||||
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)
|
||||
}
|
||||
|
||||
type formSource map[string][]string
|
||||
@ -65,7 +69,7 @@ type formSource map[string][]string
|
||||
var _ setter = formSource(nil)
|
||||
|
||||
// TrySet tries to set a value by request's form source (like map[string][]string)
|
||||
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
|
||||
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {
|
||||
return setByForm(value, field, form, tagValue, opt)
|
||||
}
|
||||
|
||||
@ -79,7 +83,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var vKind = value.Kind()
|
||||
vKind := value.Kind()
|
||||
|
||||
if vKind == reflect.Ptr {
|
||||
var isNew bool
|
||||
@ -88,14 +92,14 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
||||
isNew = true
|
||||
vPtr = reflect.New(value.Type().Elem())
|
||||
}
|
||||
isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
|
||||
isSet, err := mapping(vPtr.Elem(), field, setter, tag)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if isNew && isSetted {
|
||||
if isNew && isSet {
|
||||
value.Set(vPtr)
|
||||
}
|
||||
return isSetted, nil
|
||||
return isSet, nil
|
||||
}
|
||||
|
||||
if vKind != reflect.Struct || !field.Anonymous {
|
||||
@ -111,7 +115,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
||||
if vKind == reflect.Struct {
|
||||
tValue := value.Type()
|
||||
|
||||
var isSetted bool
|
||||
var isSet bool
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
sf := tValue.Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||
@ -121,9 +125,9 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isSetted = isSetted || ok
|
||||
isSet = isSet || ok
|
||||
}
|
||||
return isSetted, nil
|
||||
return isSet, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@ -160,7 +164,7 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
|
||||
return setter.TrySet(value, field, tagValue, setOpt)
|
||||
}
|
||||
|
||||
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
|
||||
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
|
||||
vs, ok := form[tagValue]
|
||||
if !ok && !opt.isDefaultExists {
|
||||
return false, nil
|
||||
@ -210,7 +214,7 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
||||
case reflect.Int64:
|
||||
switch value.Interface().(type) {
|
||||
case time.Duration:
|
||||
return setTimeDuration(val, value, field)
|
||||
return setTimeDuration(val, value)
|
||||
}
|
||||
return setIntField(val, 64, value)
|
||||
case reflect.Uint:
|
||||
@ -310,7 +314,6 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
||||
t := time.Unix(tv/int64(d), tv%int64(d))
|
||||
value.Set(reflect.ValueOf(t))
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
if val == "" {
|
||||
@ -360,7 +363,7 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error {
|
||||
func setTimeDuration(val string, value reflect.Value) error {
|
||||
d, err := time.ParseDuration(val)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -164,7 +164,7 @@ func TestMappingURI(t *testing.T) {
|
||||
var s struct {
|
||||
F int `uri:"field"`
|
||||
}
|
||||
err := mapUri(&s, map[string][]string{"field": {"6"}})
|
||||
err := mapURI(&s, map[string][]string{"field": {"6"}})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(6), s.F)
|
||||
}
|
||||
@ -178,6 +178,15 @@ func TestMappingForm(t *testing.T) {
|
||||
assert.Equal(t, int(6), s.F)
|
||||
}
|
||||
|
||||
func TestMapFormWithTag(t *testing.T) {
|
||||
var s struct {
|
||||
F int `externalTag:"field"`
|
||||
}
|
||||
err := MapFormWithTag(&s, map[string][]string{"field": {"6"}}, "externalTag")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(6), s.F)
|
||||
}
|
||||
|
||||
func TestMappingTime(t *testing.T) {
|
||||
var s struct {
|
||||
Time time.Time
|
||||
|
@ -32,7 +32,7 @@ func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField
|
||||
return setByForm(value, field, r.MultipartForm.Value, key, opt)
|
||||
}
|
||||
|
||||
func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
|
||||
func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Ptr:
|
||||
switch value.Interface().(type) {
|
||||
@ -48,9 +48,9 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file
|
||||
}
|
||||
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
|
||||
isSet, err = setArrayOfMultipartFormFiles(slice, field, files)
|
||||
if err != nil || !isSet {
|
||||
return isSet, err
|
||||
}
|
||||
value.Set(slice)
|
||||
return true, nil
|
||||
@ -60,14 +60,14 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file
|
||||
return false, ErrMultiFileHeader
|
||||
}
|
||||
|
||||
func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
|
||||
func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
|
||||
if value.Len() != len(files) {
|
||||
return false, ErrMultiFileHeaderLenInvalid
|
||||
}
|
||||
for i := range files {
|
||||
setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
|
||||
if err != nil || !setted {
|
||||
return setted, err
|
||||
set, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
|
||||
if err != nil || !set {
|
||||
return set, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
|
@ -5,10 +5,11 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type protobufBinding struct{}
|
||||
@ -26,7 +27,11 @@ func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
|
||||
}
|
||||
|
||||
func (protobufBinding) BindBody(body []byte, obj interface{}) error {
|
||||
if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil {
|
||||
msg, ok := obj.(proto.Message)
|
||||
if !ok {
|
||||
return errors.New("obj is not ProtoMessage")
|
||||
}
|
||||
if err := proto.Unmarshal(body, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
// Here it's same to return validate(obj), but util now we can't add
|
||||
|
@ -11,7 +11,7 @@ func (uriBinding) Name() string {
|
||||
}
|
||||
|
||||
func (uriBinding) BindUri(m map[string][]string, obj interface{}) error {
|
||||
if err := mapUri(obj, m); err != nil {
|
||||
if err := mapURI(obj, m); err != nil {
|
||||
return err
|
||||
}
|
||||
return validate(obj)
|
||||
|
172
context.go
172
context.go
@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
@ -39,7 +40,8 @@ const (
|
||||
// BodyBytesKey indicates a default body bytes key.
|
||||
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
|
||||
|
||||
const abortIndex int8 = math.MaxInt8 / 2
|
||||
// abortIndex represents a typical value used in abort functions.
|
||||
const abortIndex int8 = math.MaxInt8 >> 1
|
||||
|
||||
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
||||
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
||||
@ -53,8 +55,9 @@ type Context struct {
|
||||
index int8
|
||||
fullPath string
|
||||
|
||||
engine *Engine
|
||||
params *Params
|
||||
engine *Engine
|
||||
params *Params
|
||||
skippedNodes *[]skippedNode
|
||||
|
||||
// This mutex protect Keys map
|
||||
mu sync.RWMutex
|
||||
@ -97,6 +100,7 @@ func (c *Context) reset() {
|
||||
c.queryCache = nil
|
||||
c.formCache = nil
|
||||
*c.params = (*c.params)[:0]
|
||||
*c.skippedNodes = (*c.skippedNodes)[:0]
|
||||
}
|
||||
|
||||
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
||||
@ -218,7 +222,8 @@ func (c *Context) Error(err error) *Error {
|
||||
panic("err is nil")
|
||||
}
|
||||
|
||||
parsedError, ok := err.(*Error)
|
||||
var parsedError *Error
|
||||
ok := errors.As(err, &parsedError)
|
||||
if !ok {
|
||||
parsedError = &Error{
|
||||
Err: err,
|
||||
@ -381,6 +386,15 @@ func (c *Context) Param(key string) string {
|
||||
return c.Params.ByName(key)
|
||||
}
|
||||
|
||||
// AddParam adds param to context and
|
||||
// replaces path param key with given value for e2e testing purposes
|
||||
// Example Route: "/user/:id"
|
||||
// AddParam("id", 1)
|
||||
// Result: "/user/1"
|
||||
func (c *Context) AddParam(key, value string) {
|
||||
c.Params = append(c.Params, Param{Key: key, Value: value})
|
||||
}
|
||||
|
||||
// Query returns the keyed url query value if it exists,
|
||||
// otherwise it returns an empty string `("")`.
|
||||
// It is shortcut for `c.Request.URL.Query().Get(key)`
|
||||
@ -389,9 +403,9 @@ func (c *Context) Param(key string) string {
|
||||
// c.Query("name") == "Manu"
|
||||
// c.Query("value") == ""
|
||||
// c.Query("wtf") == ""
|
||||
func (c *Context) Query(key string) string {
|
||||
value, _ := c.GetQuery(key)
|
||||
return value
|
||||
func (c *Context) Query(key string) (value string) {
|
||||
value, _ = c.GetQuery(key)
|
||||
return
|
||||
}
|
||||
|
||||
// DefaultQuery returns the keyed url query value if it exists,
|
||||
@ -425,9 +439,9 @@ func (c *Context) GetQuery(key string) (string, bool) {
|
||||
|
||||
// QueryArray returns a slice of strings for a given query key.
|
||||
// The length of the slice depends on the number of params with the given key.
|
||||
func (c *Context) QueryArray(key string) []string {
|
||||
values, _ := c.GetQueryArray(key)
|
||||
return values
|
||||
func (c *Context) QueryArray(key string) (values []string) {
|
||||
values, _ = c.GetQueryArray(key)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Context) initQueryCache() {
|
||||
@ -442,18 +456,16 @@ func (c *Context) initQueryCache() {
|
||||
|
||||
// GetQueryArray returns a slice of strings for a given query key, plus
|
||||
// a boolean value whether at least one value exists for the given key.
|
||||
func (c *Context) GetQueryArray(key string) ([]string, bool) {
|
||||
func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
|
||||
c.initQueryCache()
|
||||
if values, ok := c.queryCache[key]; ok && len(values) > 0 {
|
||||
return values, true
|
||||
}
|
||||
return []string{}, false
|
||||
values, ok = c.queryCache[key]
|
||||
return
|
||||
}
|
||||
|
||||
// QueryMap returns a map for a given query key.
|
||||
func (c *Context) QueryMap(key string) map[string]string {
|
||||
dicts, _ := c.GetQueryMap(key)
|
||||
return dicts
|
||||
func (c *Context) QueryMap(key string) (dicts map[string]string) {
|
||||
dicts, _ = c.GetQueryMap(key)
|
||||
return
|
||||
}
|
||||
|
||||
// GetQueryMap returns a map for a given query key, plus a boolean value
|
||||
@ -465,9 +477,9 @@ func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
|
||||
|
||||
// PostForm returns the specified key from a POST urlencoded form or multipart form
|
||||
// when it exists, otherwise it returns an empty string `("")`.
|
||||
func (c *Context) PostForm(key string) string {
|
||||
value, _ := c.GetPostForm(key)
|
||||
return value
|
||||
func (c *Context) PostForm(key string) (value string) {
|
||||
value, _ = c.GetPostForm(key)
|
||||
return
|
||||
}
|
||||
|
||||
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
|
||||
@ -496,9 +508,9 @@ func (c *Context) GetPostForm(key string) (string, bool) {
|
||||
|
||||
// PostFormArray returns a slice of strings for a given form key.
|
||||
// The length of the slice depends on the number of params with the given key.
|
||||
func (c *Context) PostFormArray(key string) []string {
|
||||
values, _ := c.GetPostFormArray(key)
|
||||
return values
|
||||
func (c *Context) PostFormArray(key string) (values []string) {
|
||||
values, _ = c.GetPostFormArray(key)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Context) initFormCache() {
|
||||
@ -506,7 +518,7 @@ func (c *Context) initFormCache() {
|
||||
c.formCache = make(url.Values)
|
||||
req := c.Request
|
||||
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
|
||||
if err != http.ErrNotMultipart {
|
||||
if !errors.Is(err, http.ErrNotMultipart) {
|
||||
debugPrint("error on parse multipart form array: %v", err)
|
||||
}
|
||||
}
|
||||
@ -516,18 +528,16 @@ func (c *Context) initFormCache() {
|
||||
|
||||
// GetPostFormArray returns a slice of strings for a given form key, plus
|
||||
// a boolean value whether at least one value exists for the given key.
|
||||
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||
func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
|
||||
c.initFormCache()
|
||||
if values := c.formCache[key]; len(values) > 0 {
|
||||
return values, true
|
||||
}
|
||||
return []string{}, false
|
||||
values, ok = c.formCache[key]
|
||||
return
|
||||
}
|
||||
|
||||
// PostFormMap returns a map for a given form key.
|
||||
func (c *Context) PostFormMap(key string) map[string]string {
|
||||
dicts, _ := c.GetPostFormMap(key)
|
||||
return dicts
|
||||
func (c *Context) PostFormMap(key string) (dicts map[string]string) {
|
||||
dicts, _ = c.GetPostFormMap(key)
|
||||
return
|
||||
}
|
||||
|
||||
// GetPostFormMap returns a map for a given form key, plus a boolean value
|
||||
@ -725,19 +735,24 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e
|
||||
return bb.BindBody(body, obj)
|
||||
}
|
||||
|
||||
// ClientIP implements a best effort algorithm to return the real client IP.
|
||||
// ClientIP implements one best effort algorithm to return the real client IP.
|
||||
// It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
|
||||
// If it's it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
|
||||
// If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy,
|
||||
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
|
||||
// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy,
|
||||
// the remote IP (coming form Request.RemoteAddr) is returned.
|
||||
func (c *Context) ClientIP() string {
|
||||
switch {
|
||||
case c.engine.AppEngine:
|
||||
if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
|
||||
// Check if we're running on a trusted platform, continue running backwards if error
|
||||
if c.engine.TrustedPlatform != "" {
|
||||
// Developers can define their own header of Trusted Platform or use predefined constants
|
||||
if addr := c.requestHeader(c.engine.TrustedPlatform); addr != "" {
|
||||
return addr
|
||||
}
|
||||
case c.engine.CloudflareProxy:
|
||||
if addr := c.requestHeader("CF-Connecting-IP"); addr != "" {
|
||||
}
|
||||
|
||||
// Legacy "AppEngine" flag
|
||||
if c.engine.AppEngine {
|
||||
log.Println(`The AppEngine flag is going to be deprecated. Please check issues #2723 and #2739 and use 'TrustedPlatform: gin.PlatformGoogleAppEngine' instead.`)
|
||||
if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
@ -749,7 +764,7 @@ func (c *Context) ClientIP() string {
|
||||
|
||||
if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
|
||||
for _, headerName := range c.engine.RemoteIPHeaders {
|
||||
ip, valid := validateHeader(c.requestHeader(headerName))
|
||||
ip, valid := c.engine.validateHeader(c.requestHeader(headerName))
|
||||
if valid {
|
||||
return ip
|
||||
}
|
||||
@ -758,10 +773,21 @@ func (c *Context) ClientIP() string {
|
||||
return remoteIP.String()
|
||||
}
|
||||
|
||||
func (e *Engine) isTrustedProxy(ip net.IP) bool {
|
||||
if e.trustedCIDRs != nil {
|
||||
for _, cidr := range e.trustedCIDRs {
|
||||
if cidr.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
|
||||
// It also checks if the remoteIP is a trusted proxy or not.
|
||||
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
|
||||
// defined in Engine.TrustedProxies
|
||||
// defined by Engine.SetTrustedProxies()
|
||||
func (c *Context) RemoteIP() (net.IP, bool) {
|
||||
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
|
||||
if err != nil {
|
||||
@ -772,35 +798,25 @@ func (c *Context) RemoteIP() (net.IP, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if c.engine.trustedCIDRs != nil {
|
||||
for _, cidr := range c.engine.trustedCIDRs {
|
||||
if cidr.Contains(remoteIP) {
|
||||
return remoteIP, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return remoteIP, false
|
||||
return remoteIP, c.engine.isTrustedProxy(remoteIP)
|
||||
}
|
||||
|
||||
func validateHeader(header string) (clientIP string, valid bool) {
|
||||
func (e *Engine) validateHeader(header string) (clientIP string, valid bool) {
|
||||
if header == "" {
|
||||
return "", false
|
||||
}
|
||||
items := strings.Split(header, ",")
|
||||
for i, ipStr := range items {
|
||||
ipStr = strings.TrimSpace(ipStr)
|
||||
for i := len(items) - 1; i >= 0; i-- {
|
||||
ipStr := strings.TrimSpace(items[i])
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// We need to return the first IP in the list, but,
|
||||
// we should not early return since we need to validate that
|
||||
// the rest of the header is syntactically valid
|
||||
if i == 0 {
|
||||
clientIP = ipStr
|
||||
valid = true
|
||||
// X-Forwarded-For is appended by proxy
|
||||
// Check IPs in reverse order and stop when find untrusted proxy
|
||||
if (i == 0) || (!e.isTrustedProxy(ip)) {
|
||||
return ipStr, true
|
||||
}
|
||||
}
|
||||
return
|
||||
@ -1150,22 +1166,28 @@ func (c *Context) SetAccepted(formats ...string) {
|
||||
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
||||
/************************************/
|
||||
|
||||
// Deadline always returns that there is no deadline (ok==false),
|
||||
// maybe you want to use Request.Context().Deadline() instead.
|
||||
// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
|
||||
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
if c.Request == nil || c.Request.Context() == nil {
|
||||
return
|
||||
}
|
||||
return c.Request.Context().Deadline()
|
||||
}
|
||||
|
||||
// Done always returns nil (chan which will wait forever),
|
||||
// if you want to abort your work when the connection was closed
|
||||
// you should use Request.Context().Done() instead.
|
||||
// Done returns nil (chan which will wait forever) when c.Request has no Context.
|
||||
func (c *Context) Done() <-chan struct{} {
|
||||
return nil
|
||||
if c.Request == nil || c.Request.Context() == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Request.Context().Done()
|
||||
}
|
||||
|
||||
// Err always returns nil, maybe you want to use Request.Context().Err() instead.
|
||||
// Err returns nil when c.Request has no Context.
|
||||
func (c *Context) Err() error {
|
||||
return nil
|
||||
if c.Request == nil || c.Request.Context() == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Request.Context().Err()
|
||||
}
|
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
@ -1176,8 +1198,12 @@ func (c *Context) Value(key interface{}) interface{} {
|
||||
return c.Request
|
||||
}
|
||||
if keyAsString, ok := key.(string); ok {
|
||||
val, _ := c.Get(keyAsString)
|
||||
return val
|
||||
if val, exists := c.Get(keyAsString); exists {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
if c.Request == nil || c.Request.Context() == nil {
|
||||
return nil
|
||||
}
|
||||
return c.Request.Context().Value(key)
|
||||
}
|
||||
|
31
context_1.16_test.go
Normal file
31
context_1.16_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
//go:build !go1.17
|
||||
// +build !go1.17
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestContextFormFileFailed16(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(buf)
|
||||
mw.Close()
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
|
||||
c.engine.MaxMultipartMemory = 8 << 20
|
||||
f, err := c.FormFile("file")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, f)
|
||||
}
|
33
context_1.17_test.go
Normal file
33
context_1.17_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2021 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.
|
||||
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestContextFormFileFailed17(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(buf)
|
||||
mw.Close()
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
|
||||
c.engine.MaxMultipartMemory = 8 << 20
|
||||
assert.Panics(t, func() {
|
||||
f, err := c.FormFile("file")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, f)
|
||||
})
|
||||
}
|
@ -8,5 +8,5 @@
|
||||
package gin
|
||||
|
||||
func init() {
|
||||
defaultAppEngine = true
|
||||
defaultPlatform = PlatformGoogleAppEngine
|
||||
}
|
||||
|
175
context_test.go
175
context_test.go
@ -23,10 +23,9 @@ import (
|
||||
|
||||
"github.com/gin-contrib/sse"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var _ context.Context = &Context{}
|
||||
@ -87,19 +86,6 @@ func TestContextFormFile(t *testing.T) {
|
||||
assert.NoError(t, c.SaveUploadedFile(f, "test"))
|
||||
}
|
||||
|
||||
func TestContextFormFileFailed(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(buf)
|
||||
mw.Close()
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
|
||||
c.engine.MaxMultipartMemory = 8 << 20
|
||||
f, err := c.FormFile("file")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, f)
|
||||
}
|
||||
|
||||
func TestContextMultipartForm(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(buf)
|
||||
@ -234,7 +220,6 @@ func TestContextSetGetValues(t *testing.T) {
|
||||
assert.Exactly(t, c.MustGet("float32").(float32), float32(4.2))
|
||||
assert.Exactly(t, c.MustGet("float64").(float64), 4.2)
|
||||
assert.Exactly(t, c.MustGet("intInterface").(int), 1)
|
||||
|
||||
}
|
||||
|
||||
func TestContextGetString(t *testing.T) {
|
||||
@ -300,7 +285,7 @@ func TestContextGetStringSlice(t *testing.T) {
|
||||
|
||||
func TestContextGetStringMap(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
var m = make(map[string]interface{})
|
||||
m := make(map[string]interface{})
|
||||
m["foo"] = 1
|
||||
c.Set("map", m)
|
||||
|
||||
@ -310,7 +295,7 @@ func TestContextGetStringMap(t *testing.T) {
|
||||
|
||||
func TestContextGetStringMapString(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
var m = make(map[string]string)
|
||||
m := make(map[string]string)
|
||||
m["foo"] = "bar"
|
||||
c.Set("map", m)
|
||||
|
||||
@ -320,7 +305,7 @@ func TestContextGetStringMapString(t *testing.T) {
|
||||
|
||||
func TestContextGetStringMapStringSlice(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
var m = make(map[string][]string)
|
||||
m := make(map[string][]string)
|
||||
m["foo"] = []string{"foo"}
|
||||
c.Set("map", m)
|
||||
|
||||
@ -369,15 +354,12 @@ func TestContextHandlerNames(t *testing.T) {
|
||||
}
|
||||
|
||||
func handlerNameTest(c *Context) {
|
||||
|
||||
}
|
||||
|
||||
func handlerNameTest2(c *Context) {
|
||||
|
||||
}
|
||||
|
||||
var handlerTest HandlerFunc = func(c *Context) {
|
||||
|
||||
}
|
||||
|
||||
func TestContextHandler(t *testing.T) {
|
||||
@ -659,8 +641,7 @@ func TestContextBodyAllowedForStatus(t *testing.T) {
|
||||
assert.True(t, true, bodyAllowedForStatus(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
type TestPanicRender struct {
|
||||
}
|
||||
type TestPanicRender struct{}
|
||||
|
||||
func (*TestPanicRender) Render(http.ResponseWriter) error {
|
||||
return errors.New("TestPanicRender")
|
||||
@ -1329,7 +1310,7 @@ func TestContextAbortWithStatusJSON(t *testing.T) {
|
||||
_, err := buf.ReadFrom(w.Body)
|
||||
assert.NoError(t, err)
|
||||
jsonStringBody := buf.String()
|
||||
assert.Equal(t, fmt.Sprint("{\"foo\":\"fooValue\",\"bar\":\"barValue\"}"), jsonStringBody)
|
||||
assert.Equal(t, "{\"foo\":\"fooValue\",\"bar\":\"barValue\"}", jsonStringBody)
|
||||
}
|
||||
|
||||
func TestContextError(t *testing.T) {
|
||||
@ -1410,7 +1391,7 @@ func TestContextClientIP(t *testing.T) {
|
||||
|
||||
c.Request.Header.Del("X-Forwarded-For")
|
||||
c.Request.Header.Del("X-Real-IP")
|
||||
c.engine.AppEngine = true
|
||||
c.engine.TrustedPlatform = PlatformGoogleAppEngine
|
||||
assert.Equal(t, "50.50.50.50", c.ClientIP())
|
||||
|
||||
c.Request.Header.Del("X-Appengine-Remote-Addr")
|
||||
@ -1428,13 +1409,17 @@ func TestContextClientIP(t *testing.T) {
|
||||
c.engine.RemoteIPHeaders = []string{"X-Forwarded-For"}
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// Disabled TrustedProxies feature
|
||||
_ = c.engine.SetTrustedProxies(nil)
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// Last proxy is trusted, but the RemoteAddr is not
|
||||
_ = c.engine.SetTrustedProxies([]string{"30.30.30.30"})
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// Only trust RemoteAddr
|
||||
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40"})
|
||||
assert.Equal(t, "20.20.20.20", c.ClientIP())
|
||||
assert.Equal(t, "30.30.30.30", c.ClientIP())
|
||||
|
||||
// All steps are trusted
|
||||
_ = c.engine.SetTrustedProxies([]string{"40.40.40.40", "30.30.30.30", "20.20.20.20"})
|
||||
@ -1470,19 +1455,39 @@ func TestContextClientIP(t *testing.T) {
|
||||
assert.Equal(t, "10.10.10.10", c.ClientIP())
|
||||
|
||||
c.engine.RemoteIPHeaders = []string{}
|
||||
c.engine.TrustedPlatform = PlatformGoogleAppEngine
|
||||
assert.Equal(t, "50.50.50.50", c.ClientIP())
|
||||
|
||||
// Use custom TrustedPlatform header
|
||||
c.engine.TrustedPlatform = "X-CDN-IP"
|
||||
c.Request.Header.Set("X-CDN-IP", "80.80.80.80")
|
||||
assert.Equal(t, "80.80.80.80", c.ClientIP())
|
||||
// wrong header
|
||||
c.engine.TrustedPlatform = "X-Wrong-Header"
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
c.Request.Header.Del("X-CDN-IP")
|
||||
// TrustedPlatform is empty
|
||||
c.engine.TrustedPlatform = ""
|
||||
assert.Equal(t, "40.40.40.40", c.ClientIP())
|
||||
|
||||
// Test the legacy flag
|
||||
c.engine.AppEngine = true
|
||||
assert.Equal(t, "50.50.50.50", c.ClientIP())
|
||||
c.engine.AppEngine = false
|
||||
c.engine.TrustedPlatform = PlatformGoogleAppEngine
|
||||
|
||||
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
|
||||
c.engine.TrustedPlatform = PlatformCloudflare
|
||||
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())
|
||||
|
||||
c.engine.TrustedPlatform = ""
|
||||
|
||||
// no port
|
||||
c.Request.RemoteAddr = "50.50.50.50"
|
||||
assert.Empty(t, c.ClientIP())
|
||||
@ -1494,6 +1499,7 @@ func resetContextForClientIPTests(c *Context) {
|
||||
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.TrustedPlatform = ""
|
||||
c.engine.AppEngine = false
|
||||
}
|
||||
|
||||
@ -1536,6 +1542,7 @@ func TestContextBindWithJSON(t *testing.T) {
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
|
||||
func TestContextBindWithXML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
@ -2048,3 +2055,111 @@ func TestRemoteIPFail(t *testing.T) {
|
||||
assert.Nil(t, ip)
|
||||
assert.False(t, trust)
|
||||
}
|
||||
|
||||
func TestContextWithFallbackDeadlineFromRequestContext(t *testing.T) {
|
||||
c := &Context{}
|
||||
deadline, ok := c.Deadline()
|
||||
assert.Zero(t, deadline)
|
||||
assert.False(t, ok)
|
||||
|
||||
c2 := &Context{}
|
||||
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
|
||||
d := time.Now().Add(time.Second)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||
defer cancel()
|
||||
c2.Request = c2.Request.WithContext(ctx)
|
||||
deadline, ok = c2.Deadline()
|
||||
assert.Equal(t, d, deadline)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestContextWithFallbackDoneFromRequestContext(t *testing.T) {
|
||||
c := &Context{}
|
||||
assert.Nil(t, c.Done())
|
||||
|
||||
c2 := &Context{}
|
||||
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c2.Request = c2.Request.WithContext(ctx)
|
||||
cancel()
|
||||
assert.NotNil(t, <-c2.Done())
|
||||
}
|
||||
|
||||
func TestContextWithFallbackErrFromRequestContext(t *testing.T) {
|
||||
c := &Context{}
|
||||
assert.Nil(t, c.Err())
|
||||
|
||||
c2 := &Context{}
|
||||
c2.Request, _ = http.NewRequest(http.MethodGet, "/", nil)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c2.Request = c2.Request.WithContext(ctx)
|
||||
cancel()
|
||||
|
||||
assert.EqualError(t, c2.Err(), context.Canceled.Error())
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
|
||||
func TestContextWithFallbackValueFromRequestContext(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
getContextAndKey func() (*Context, interface{})
|
||||
value interface{}
|
||||
}{
|
||||
{
|
||||
name: "c with struct context key",
|
||||
getContextAndKey: func() (*Context, interface{}) {
|
||||
var key struct{}
|
||||
c := &Context{}
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
c.Request = c.Request.WithContext(context.WithValue(context.TODO(), key, "value"))
|
||||
return c, key
|
||||
},
|
||||
value: "value",
|
||||
},
|
||||
{
|
||||
name: "c with string context key",
|
||||
getContextAndKey: func() (*Context, interface{}) {
|
||||
c := &Context{}
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
c.Request = c.Request.WithContext(context.WithValue(context.TODO(), contextKey("key"), "value"))
|
||||
return c, contextKey("key")
|
||||
},
|
||||
value: "value",
|
||||
},
|
||||
{
|
||||
name: "c with nil http.Request",
|
||||
getContextAndKey: func() (*Context, interface{}) {
|
||||
c := &Context{}
|
||||
return c, "key"
|
||||
},
|
||||
value: nil,
|
||||
},
|
||||
{
|
||||
name: "c with nil http.Request.Context()",
|
||||
getContextAndKey: func() (*Context, interface{}) {
|
||||
c := &Context{}
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
return c, "key"
|
||||
},
|
||||
value: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c, key := tt.getContextAndKey()
|
||||
assert.Equal(t, tt.value, c.Value(key))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextAddParam(t *testing.T) {
|
||||
c := &Context{}
|
||||
id := "id"
|
||||
value := "1"
|
||||
c.AddParam(id, value)
|
||||
|
||||
v, ok := c.Params.Get(id)
|
||||
assert.Equal(t, ok, true)
|
||||
assert.Equal(t, value, v)
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ func (e TestErr) Error() string { return string(e) }
|
||||
// TestErrorUnwrap tests the behavior of gin.Error with "errors.Is()" and "errors.As()".
|
||||
// "errors.Is()" and "errors.As()" have been added to the standard library in go 1.13.
|
||||
func TestErrorUnwrap(t *testing.T) {
|
||||
innerErr := TestErr("somme error")
|
||||
innerErr := TestErr("some error")
|
||||
|
||||
// 2 layers of wrapping : use 'fmt.Errorf("%w")' to wrap a gin.Error{}, which itself wraps innerErr
|
||||
err := fmt.Errorf("wrapped: %w", &Error{
|
||||
|
126
gin.go
126
gin.go
@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -25,7 +26,9 @@ var (
|
||||
default405Body = []byte("405 method not allowed")
|
||||
)
|
||||
|
||||
var defaultAppEngine bool
|
||||
var defaultPlatform string
|
||||
|
||||
var defaultTrustedCIDRs = []*net.IPNet{{IP: net.IP{0x0, 0x0, 0x0, 0x0}, Mask: net.IPMask{0x0, 0x0, 0x0, 0x0}}} // 0.0.0.0/0
|
||||
|
||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||
type HandlerFunc func(*Context)
|
||||
@ -52,6 +55,16 @@ type RouteInfo struct {
|
||||
// RoutesInfo defines a RouteInfo array.
|
||||
type RoutesInfo []RouteInfo
|
||||
|
||||
// Trusted platforms
|
||||
const (
|
||||
// When running on Google App Engine. Trust X-Appengine-Remote-Addr
|
||||
// for determining the client's IP
|
||||
PlatformGoogleAppEngine = "X-Appengine-Remote-Addr"
|
||||
// When using Cloudflare's CDN. Trust CF-Connecting-IP for determining
|
||||
// the client's IP
|
||||
PlatformCloudflare = "CF-Connecting-IP"
|
||||
)
|
||||
|
||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||
// Create an instance of Engine, by using New() or Default()
|
||||
type Engine struct {
|
||||
@ -89,26 +102,11 @@ type Engine struct {
|
||||
// `(*gin.Context).Request.RemoteAddr`.
|
||||
ForwardedByClientIP bool
|
||||
|
||||
// List of headers used to obtain the client IP when
|
||||
// `(*gin.Engine).ForwardedByClientIP` is `true` and
|
||||
// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
|
||||
// network origins of `(*gin.Engine).TrustedProxies`.
|
||||
RemoteIPHeaders []string
|
||||
|
||||
// List of network origins (IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
|
||||
// IPv6 CIDRs) from which to trust request's headers that contain
|
||||
// alternative client IP when `(*gin.Engine).ForwardedByClientIP` is
|
||||
// `true`.
|
||||
TrustedProxies []string
|
||||
|
||||
// DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.GoogleAppEngine` INSTEAD
|
||||
// #726 #755 If enabled, it will trust some headers starting with
|
||||
// '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
|
||||
|
||||
@ -117,14 +115,24 @@ type Engine struct {
|
||||
// as url.Path gonna be used, which is already unescaped.
|
||||
UnescapePathValues bool
|
||||
|
||||
// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
|
||||
// method call.
|
||||
MaxMultipartMemory int64
|
||||
|
||||
// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
|
||||
// See the PR #1817 and issue #1644
|
||||
RemoveExtraSlash bool
|
||||
|
||||
// List of headers used to obtain the client IP when
|
||||
// `(*gin.Engine).ForwardedByClientIP` is `true` and
|
||||
// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
|
||||
// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
|
||||
RemoteIPHeaders []string
|
||||
|
||||
// If set to a constant of value gin.Platform*, trusts the headers set by
|
||||
// that platform, for example to determine the client IP
|
||||
TrustedPlatform string
|
||||
|
||||
// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
|
||||
// method call.
|
||||
MaxMultipartMemory int64
|
||||
|
||||
delims render.Delims
|
||||
secureJSONPrefix string
|
||||
HTMLRender render.HTMLRender
|
||||
@ -136,6 +144,8 @@ type Engine struct {
|
||||
pool sync.Pool
|
||||
trees methodTrees
|
||||
maxParams uint16
|
||||
maxSections uint16
|
||||
trustedProxies []string
|
||||
trustedCIDRs []*net.IPNet
|
||||
}
|
||||
|
||||
@ -163,8 +173,7 @@ func New() *Engine {
|
||||
HandleMethodNotAllowed: false,
|
||||
ForwardedByClientIP: true,
|
||||
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
|
||||
TrustedProxies: []string{"0.0.0.0/0"},
|
||||
AppEngine: defaultAppEngine,
|
||||
TrustedPlatform: defaultPlatform,
|
||||
UseRawPath: false,
|
||||
RemoveExtraSlash: false,
|
||||
UnescapePathValues: true,
|
||||
@ -172,6 +181,8 @@ func New() *Engine {
|
||||
trees: make(methodTrees, 0, 9),
|
||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||
secureJSONPrefix: "while(1);",
|
||||
trustedProxies: []string{"0.0.0.0/0"},
|
||||
trustedCIDRs: defaultTrustedCIDRs,
|
||||
}
|
||||
engine.RouterGroup.engine = engine
|
||||
engine.pool.New = func() interface{} {
|
||||
@ -190,7 +201,8 @@ func Default() *Engine {
|
||||
|
||||
func (engine *Engine) allocateContext() *Context {
|
||||
v := make(Params, 0, engine.maxParams)
|
||||
return &Context{engine: engine, params: &v}
|
||||
skippedNodes := make([]skippedNode, 0, engine.maxSections)
|
||||
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
|
||||
}
|
||||
|
||||
// Delims sets template left and right delims and returns a Engine instance.
|
||||
@ -253,7 +265,7 @@ func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
||||
engine.rebuild404Handlers()
|
||||
}
|
||||
|
||||
// NoMethod sets the handlers called when... TODO.
|
||||
// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
|
||||
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
||||
engine.noMethod = handlers
|
||||
engine.rebuild405Handlers()
|
||||
@ -296,6 +308,10 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
||||
engine.maxParams = paramsCount
|
||||
}
|
||||
|
||||
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
|
||||
engine.maxSections = sectionsCount
|
||||
}
|
||||
}
|
||||
|
||||
// Routes returns a slice of registered routes, including some useful information, such as:
|
||||
@ -330,9 +346,9 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
||||
func (engine *Engine) Run(addr ...string) (err error) {
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
err = engine.parseTrustedProxies()
|
||||
if err != nil {
|
||||
return err
|
||||
if engine.isUnsafeTrustedProxies() {
|
||||
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
address := resolveAddress(addr)
|
||||
@ -342,12 +358,12 @@ func (engine *Engine) Run(addr ...string) (err error) {
|
||||
}
|
||||
|
||||
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
||||
if engine.TrustedProxies == nil {
|
||||
if engine.trustedProxies == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cidr := make([]*net.IPNet, 0, len(engine.TrustedProxies))
|
||||
for _, trustedProxy := range engine.TrustedProxies {
|
||||
cidr := make([]*net.IPNet, 0, len(engine.trustedProxies))
|
||||
for _, trustedProxy := range engine.trustedProxies {
|
||||
if !strings.Contains(trustedProxy, "/") {
|
||||
ip := parseIP(trustedProxy)
|
||||
if ip == nil {
|
||||
@ -370,13 +386,25 @@ func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
||||
return cidr, nil
|
||||
}
|
||||
|
||||
// SetTrustedProxies set Engine.TrustedProxies
|
||||
// SetTrustedProxies set a list of network origins (IPv4 addresses,
|
||||
// IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust
|
||||
// request's headers that contain alternative client IP when
|
||||
// `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies`
|
||||
// feature is enabled by default, and it also trusts all proxies
|
||||
// by default. If you want to disable this feature, use
|
||||
// Engine.SetTrustedProxies(nil), then Context.ClientIP() will
|
||||
// return the remote address directly.
|
||||
func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
|
||||
engine.TrustedProxies = trustedProxies
|
||||
engine.trustedProxies = trustedProxies
|
||||
return engine.parseTrustedProxies()
|
||||
}
|
||||
|
||||
// parseTrustedProxies parse Engine.TrustedProxies to Engine.trustedCIDRs
|
||||
// isUnsafeTrustedProxies compares Engine.trustedCIDRs and defaultTrustedCIDRs, it's not safe if equal (returns true)
|
||||
func (engine *Engine) isUnsafeTrustedProxies() bool {
|
||||
return reflect.DeepEqual(engine.trustedCIDRs, defaultTrustedCIDRs)
|
||||
}
|
||||
|
||||
// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
|
||||
func (engine *Engine) parseTrustedProxies() error {
|
||||
trustedCIDRs, err := engine.prepareTrustedCIDRs()
|
||||
engine.trustedCIDRs = trustedCIDRs
|
||||
@ -404,9 +432,9 @@ 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
|
||||
if engine.isUnsafeTrustedProxies() {
|
||||
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
|
||||
@ -420,9 +448,9 @@ 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
|
||||
if engine.isUnsafeTrustedProxies() {
|
||||
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unix", file)
|
||||
@ -443,9 +471,9 @@ 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
|
||||
if engine.isUnsafeTrustedProxies() {
|
||||
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
|
||||
@ -464,9 +492,9 @@ 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
|
||||
if engine.isUnsafeTrustedProxies() {
|
||||
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
err = http.Serve(listener, engine)
|
||||
@ -517,7 +545,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
}
|
||||
root := t[i].root
|
||||
// Find route in tree
|
||||
value := root.getValue(rPath, c.params, unescape)
|
||||
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
|
||||
if value.params != nil {
|
||||
c.Params = *value.params
|
||||
}
|
||||
@ -528,7 +556,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
c.writermem.WriteHeaderNow()
|
||||
return
|
||||
}
|
||||
if httpMethod != "CONNECT" && rPath != "/" {
|
||||
if httpMethod != http.MethodConnect && rPath != "/" {
|
||||
if value.tsr && engine.RedirectTrailingSlash {
|
||||
redirectTrailingSlash(c)
|
||||
return
|
||||
@ -545,7 +573,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
if tree.method == httpMethod {
|
||||
continue
|
||||
}
|
||||
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
|
||||
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
|
||||
c.handlers = engine.allNoMethod
|
||||
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||
return
|
||||
|
@ -22,7 +22,15 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testRequest(t *testing.T, url string) {
|
||||
// params[0]=url example:http://127.0.0.1:8080/index (cannot be empty)
|
||||
// params[1]=response status (custom compare status) default:"200 OK"
|
||||
// params[2]=response body (custom compare content) default:"it worked"
|
||||
func testRequest(t *testing.T, params ...string) {
|
||||
|
||||
if len(params) == 0 {
|
||||
t.Fatal("url cannot be empty")
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
@ -30,14 +38,27 @@ func testRequest(t *testing.T, url string) {
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
resp, err := client.Get(params[0])
|
||||
assert.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, ioerr := ioutil.ReadAll(resp.Body)
|
||||
assert.NoError(t, ioerr)
|
||||
assert.Equal(t, "it worked", string(body), "resp body should match")
|
||||
assert.Equal(t, "200 OK", resp.Status, "should get a 200")
|
||||
|
||||
var responseStatus = "200 OK"
|
||||
if len(params) > 1 && params[1] != "" {
|
||||
responseStatus = params[1]
|
||||
}
|
||||
|
||||
var responseBody = "it worked"
|
||||
if len(params) > 2 && params[2] != "" {
|
||||
responseBody = params[2]
|
||||
}
|
||||
|
||||
assert.Equal(t, responseStatus, resp.Status, "should get a "+responseStatus)
|
||||
if responseStatus == "200 OK" {
|
||||
assert.Equal(t, responseBody, string(body), "resp body should match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunEmpty(t *testing.T) {
|
||||
@ -55,6 +76,12 @@ func TestRunEmpty(t *testing.T) {
|
||||
testRequest(t, "http://localhost:8080/example")
|
||||
}
|
||||
|
||||
func TestBadTrustedCIDRs(t *testing.T) {
|
||||
router := New()
|
||||
assert.Error(t, router.SetTrustedProxies([]string{"hello/world"}))
|
||||
}
|
||||
|
||||
/* legacy tests
|
||||
func TestBadTrustedCIDRsForRun(t *testing.T) {
|
||||
os.Setenv("PORT", "")
|
||||
router := New()
|
||||
@ -122,6 +149,7 @@ func TestBadTrustedCIDRsForRunTLS(t *testing.T) {
|
||||
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()
|
||||
@ -373,3 +401,149 @@ func testGetRequestHandler(t *testing.T, h http.Handler, url string) {
|
||||
assert.Equal(t, "it worked", w.Body.String(), "resp body should match")
|
||||
assert.Equal(t, 200, w.Code, "should get a 200")
|
||||
}
|
||||
|
||||
func TestTreeRunDynamicRouting(t *testing.T) {
|
||||
router := New()
|
||||
router.GET("/aa/*xx", func(c *Context) { c.String(http.StatusOK, "/aa/*xx") })
|
||||
router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") })
|
||||
router.GET("/", func(c *Context) { c.String(http.StatusOK, "home") })
|
||||
router.GET("/:cc", func(c *Context) { c.String(http.StatusOK, "/:cc") })
|
||||
router.GET("/c1/:dd/e", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e") })
|
||||
router.GET("/c1/:dd/e1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e1") })
|
||||
router.GET("/c1/:dd/f1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f1") })
|
||||
router.GET("/c1/:dd/f2", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f2") })
|
||||
router.GET("/:cc/cc", func(c *Context) { c.String(http.StatusOK, "/:cc/cc") })
|
||||
router.GET("/:cc/:dd/ee", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/ee") })
|
||||
router.GET("/:cc/:dd/f", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/f") })
|
||||
router.GET("/:cc/:dd/:ee/ff", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/ff") })
|
||||
router.GET("/:cc/:dd/:ee/:ff/gg", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/gg") })
|
||||
router.GET("/:cc/:dd/:ee/:ff/:gg/hh", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/:gg/hh") })
|
||||
router.GET("/get/test/abc/", func(c *Context) { c.String(http.StatusOK, "/get/test/abc/") })
|
||||
router.GET("/get/:param/abc/", func(c *Context) { c.String(http.StatusOK, "/get/:param/abc/") })
|
||||
router.GET("/something/:paramname/thirdthing", func(c *Context) { c.String(http.StatusOK, "/something/:paramname/thirdthing") })
|
||||
router.GET("/something/secondthing/test", func(c *Context) { c.String(http.StatusOK, "/something/secondthing/test") })
|
||||
router.GET("/get/abc", func(c *Context) { c.String(http.StatusOK, "/get/abc") })
|
||||
router.GET("/get/:param", func(c *Context) { c.String(http.StatusOK, "/get/:param") })
|
||||
router.GET("/get/abc/123abc", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc") })
|
||||
router.GET("/get/abc/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/:param") })
|
||||
router.GET("/get/abc/123abc/xxx8", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8") })
|
||||
router.GET("/get/abc/123abc/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/:param") })
|
||||
router.GET("/get/abc/123abc/xxx8/1234", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234") })
|
||||
router.GET("/get/abc/123abc/xxx8/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/:param") })
|
||||
router.GET("/get/abc/123abc/xxx8/1234/ffas", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/ffas") })
|
||||
router.GET("/get/abc/123abc/xxx8/1234/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/:param") })
|
||||
router.GET("/get/abc/123abc/xxx8/1234/kkdd/12c", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/kkdd/12c") })
|
||||
router.GET("/get/abc/123abc/xxx8/1234/kkdd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abc/xxx8/1234/kkdd/:param") })
|
||||
router.GET("/get/abc/:param/test", func(c *Context) { c.String(http.StatusOK, "/get/abc/:param/test") })
|
||||
router.GET("/get/abc/123abd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abd/:param") })
|
||||
router.GET("/get/abc/123abddd/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abddd/:param") })
|
||||
router.GET("/get/abc/123/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123/:param") })
|
||||
router.GET("/get/abc/123abg/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abg/:param") })
|
||||
router.GET("/get/abc/123abf/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abf/:param") })
|
||||
router.GET("/get/abc/123abfff/:param", func(c *Context) { c.String(http.StatusOK, "/get/abc/123abfff/:param") })
|
||||
|
||||
ts := httptest.NewServer(router)
|
||||
defer ts.Close()
|
||||
|
||||
testRequest(t, ts.URL+"/", "", "home")
|
||||
testRequest(t, ts.URL+"/aa/aa", "", "/aa/*xx")
|
||||
testRequest(t, ts.URL+"/ab/ab", "", "/ab/*xx")
|
||||
testRequest(t, ts.URL+"/all", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc")
|
||||
testRequest(t, ts.URL+"/a/cc", "", "/:cc/cc")
|
||||
testRequest(t, ts.URL+"/c1/d/e", "", "/c1/:dd/e")
|
||||
testRequest(t, ts.URL+"/c1/d/e1", "", "/c1/:dd/e1")
|
||||
testRequest(t, ts.URL+"/c1/d/ee", "", "/:cc/:dd/ee")
|
||||
testRequest(t, ts.URL+"/c1/d/f", "", "/:cc/:dd/f")
|
||||
testRequest(t, ts.URL+"/c/d/ee", "", "/:cc/:dd/ee")
|
||||
testRequest(t, ts.URL+"/c/d/e/ff", "", "/:cc/:dd/:ee/ff")
|
||||
testRequest(t, ts.URL+"/c/d/e/f/gg", "", "/:cc/:dd/:ee/:ff/gg")
|
||||
testRequest(t, ts.URL+"/c/d/e/f/g/hh", "", "/:cc/:dd/:ee/:ff/:gg/hh")
|
||||
testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh", "", "/:cc/:dd/:ee/:ff/:gg/hh")
|
||||
testRequest(t, ts.URL+"/a", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/d", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/ad", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/dd", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/aa", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/aaa", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/aaa/cc", "", "/:cc/cc")
|
||||
testRequest(t, ts.URL+"/ab", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/abb", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/abb/cc", "", "/:cc/cc")
|
||||
testRequest(t, ts.URL+"/dddaa", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/allxxxx", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/alldd", "", "/:cc")
|
||||
testRequest(t, ts.URL+"/cc/cc", "", "/:cc/cc")
|
||||
testRequest(t, ts.URL+"/ccc/cc", "", "/:cc/cc")
|
||||
testRequest(t, ts.URL+"/deedwjfs/cc", "", "/:cc/cc")
|
||||
testRequest(t, ts.URL+"/acllcc/cc", "", "/:cc/cc")
|
||||
testRequest(t, ts.URL+"/get/test/abc/", "", "/get/test/abc/")
|
||||
testRequest(t, ts.URL+"/get/testaa/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/te/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/xx/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/tt/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/a/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/t/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/aa/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/get/abas/abc/", "", "/get/:param/abc/")
|
||||
testRequest(t, ts.URL+"/something/secondthing/test", "", "/something/secondthing/test")
|
||||
testRequest(t, ts.URL+"/something/secondthingaaaa/thirdthing", "", "/something/:paramname/thirdthing")
|
||||
testRequest(t, ts.URL+"/something/abcdad/thirdthing", "", "/something/:paramname/thirdthing")
|
||||
testRequest(t, ts.URL+"/something/se/thirdthing", "", "/something/:paramname/thirdthing")
|
||||
testRequest(t, ts.URL+"/something/s/thirdthing", "", "/something/:paramname/thirdthing")
|
||||
testRequest(t, ts.URL+"/something/secondthing/thirdthing", "", "/something/:paramname/thirdthing")
|
||||
testRequest(t, ts.URL+"/get/abc", "", "/get/abc")
|
||||
testRequest(t, ts.URL+"/get/a", "", "/get/:param")
|
||||
testRequest(t, ts.URL+"/get/abz", "", "/get/:param")
|
||||
testRequest(t, ts.URL+"/get/12a", "", "/get/:param")
|
||||
testRequest(t, ts.URL+"/get/abcd", "", "/get/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc", "", "/get/abc/123abc")
|
||||
testRequest(t, ts.URL+"/get/abc/12", "", "/get/abc/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123ab", "", "/get/abc/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/xyz", "", "/get/abc/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abcddxx", "", "/get/abc/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8", "", "/get/abc/123abc/xxx8")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/x", "", "/get/abc/123abc/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx", "", "/get/abc/123abc/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/abc", "", "/get/abc/123abc/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8xxas", "", "/get/abc/123abc/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234", "", "/get/abc/123abc/xxx8/1234")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1", "", "/get/abc/123abc/xxx8/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/123", "", "/get/abc/123abc/xxx8/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/78k", "", "/get/abc/123abc/xxx8/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234xxxd", "", "/get/abc/123abc/xxx8/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffas", "", "/get/abc/123abc/xxx8/1234/ffas")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/f", "", "/get/abc/123abc/xxx8/1234/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffa", "", "/get/abc/123abc/xxx8/1234/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kka", "", "/get/abc/123abc/xxx8/1234/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/ffas321", "", "/get/abc/123abc/xxx8/1234/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12c", "", "/get/abc/123abc/xxx8/1234/kkdd/12c")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/1", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12b", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/34", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abc/xxx8/1234/kkdd/12c2e3", "", "/get/abc/123abc/xxx8/1234/kkdd/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/12/test", "", "/get/abc/:param/test")
|
||||
testRequest(t, ts.URL+"/get/abc/123abdd/test", "", "/get/abc/:param/test")
|
||||
testRequest(t, ts.URL+"/get/abc/123abdddf/test", "", "/get/abc/:param/test")
|
||||
testRequest(t, ts.URL+"/get/abc/123ab/test", "", "/get/abc/:param/test")
|
||||
testRequest(t, ts.URL+"/get/abc/123abgg/test", "", "/get/abc/:param/test")
|
||||
testRequest(t, ts.URL+"/get/abc/123abff/test", "", "/get/abc/:param/test")
|
||||
testRequest(t, ts.URL+"/get/abc/123abffff/test", "", "/get/abc/:param/test")
|
||||
testRequest(t, ts.URL+"/get/abc/123abd/test", "", "/get/abc/123abd/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abddd/test", "", "/get/abc/123abddd/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123/test22", "", "/get/abc/123/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abg/test", "", "/get/abc/123abg/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abf/testss", "", "/get/abc/123abf/:param")
|
||||
testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param")
|
||||
// 404 not found
|
||||
testRequest(t, ts.URL+"/c/d/e", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/c/d/e1", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/c/d/eee", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/c1/d/eee", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/c1/d/e2", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh1", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/a/dd", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found")
|
||||
}
|
||||
|
57
gin_test.go
57
gin_test.go
@ -539,19 +539,15 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||
// valid ipv4 cidr
|
||||
{
|
||||
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("0.0.0.0/0")}
|
||||
r.TrustedProxies = []string{"0.0.0.0/0"}
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies([]string{"0.0.0.0/0"})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid ipv4 cidr
|
||||
{
|
||||
r.TrustedProxies = []string{"192.168.1.33/33"}
|
||||
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies([]string{"192.168.1.33/33"})
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@ -559,19 +555,16 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||
// valid ipv4 address
|
||||
{
|
||||
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("192.168.1.33/32")}
|
||||
r.TrustedProxies = []string{"192.168.1.33"}
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies([]string{"192.168.1.33"})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid ipv4 address
|
||||
{
|
||||
r.TrustedProxies = []string{"192.168.1.256"}
|
||||
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies([]string{"192.168.1.256"})
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@ -579,19 +572,15 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||
// valid ipv6 address
|
||||
{
|
||||
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("2002:0000:0000:1234:abcd:ffff:c0a8:0101/128")}
|
||||
r.TrustedProxies = []string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"}
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies([]string{"2002:0000:0000:1234:abcd:ffff:c0a8:0101"})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid ipv6 address
|
||||
{
|
||||
r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"}
|
||||
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101"})
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@ -599,19 +588,15 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||
// valid ipv6 cidr
|
||||
{
|
||||
expectedTrustedCIDRs := []*net.IPNet{parseCIDR("::/0")}
|
||||
r.TrustedProxies = []string{"::/0"}
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies([]string{"::/0"})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid ipv6 cidr
|
||||
{
|
||||
r.TrustedProxies = []string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"}
|
||||
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies([]string{"gggg:0000:0000:1234:abcd:ffff:c0a8:0101/129"})
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@ -623,36 +608,32 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||
parseCIDR("192.168.0.0/16"),
|
||||
parseCIDR("172.16.0.1/32"),
|
||||
}
|
||||
r.TrustedProxies = []string{
|
||||
err := r.SetTrustedProxies([]string{
|
||||
"::/0",
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.1",
|
||||
}
|
||||
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedTrustedCIDRs, trustedCIDRs)
|
||||
assert.Equal(t, expectedTrustedCIDRs, r.trustedCIDRs)
|
||||
}
|
||||
|
||||
// invalid combination
|
||||
{
|
||||
r.TrustedProxies = []string{
|
||||
err := r.SetTrustedProxies([]string{
|
||||
"::/0",
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.256",
|
||||
}
|
||||
_, err := r.prepareTrustedCIDRs()
|
||||
})
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// nil value
|
||||
{
|
||||
r.TrustedProxies = nil
|
||||
trustedCIDRs, err := r.prepareTrustedCIDRs()
|
||||
err := r.SetTrustedProxies(nil)
|
||||
|
||||
assert.Nil(t, trustedCIDRs)
|
||||
assert.Nil(t, r.trustedCIDRs)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
|
14
go.mod
14
go.mod
@ -4,12 +4,12 @@ go 1.13
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/sse v0.1.0
|
||||
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/go-playground/validator/v10 v10.9.0
|
||||
github.com/goccy/go-json v0.7.10
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/ugorji/go/codec v1.2.6
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
96
go.sum
96
go.sum
@ -1,3 +1,4 @@
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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=
|
||||
@ -5,51 +6,76 @@ 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/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
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.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/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
|
||||
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/goccy/go-json v0.7.10 h1:ulhbuNe1JqE68nMRXXTJRrUu0uhouf0VevLINxQq4Ec=
|
||||
github.com/goccy/go-json v0.7.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
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/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -118,7 +118,10 @@ func TestMiddlewareNoMethodEnabled(t *testing.T) {
|
||||
func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
||||
signature := ""
|
||||
router := New()
|
||||
|
||||
// NoMethod disabled
|
||||
router.HandleMethodNotAllowed = false
|
||||
|
||||
router.Use(func(c *Context) {
|
||||
signature += "A"
|
||||
c.Next()
|
||||
@ -144,6 +147,7 @@ func TestMiddlewareNoMethodDisabled(t *testing.T) {
|
||||
router.POST("/", func(c *Context) {
|
||||
signature += " XX "
|
||||
})
|
||||
|
||||
// RUN
|
||||
w := performRequest(router, "GET", "/")
|
||||
|
||||
|
6
mode.go
6
mode.go
@ -41,8 +41,10 @@ var DefaultWriter io.Writer = os.Stdout
|
||||
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
|
||||
var DefaultErrorWriter io.Writer = os.Stderr
|
||||
|
||||
var ginMode = debugCode
|
||||
var modeName = DebugMode
|
||||
var (
|
||||
ginMode = debugCode
|
||||
modeName = DebugMode
|
||||
)
|
||||
|
||||
func init() {
|
||||
mode := os.Getenv(EnvGinMode)
|
||||
|
10
recovery.go
10
recovery.go
@ -6,6 +6,7 @@ package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -34,7 +35,7 @@ func Recovery() HandlerFunc {
|
||||
return RecoveryWithWriter(DefaultErrorWriter)
|
||||
}
|
||||
|
||||
//CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
|
||||
// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
|
||||
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
|
||||
return RecoveryWithWriter(DefaultErrorWriter, handle)
|
||||
}
|
||||
@ -60,7 +61,8 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||
// condition that warrants a panic stack trace.
|
||||
var brokenPipe bool
|
||||
if ne, ok := err.(*net.OpError); ok {
|
||||
if se, ok := ne.Err.(*os.SyscallError); ok {
|
||||
var se *os.SyscallError
|
||||
if errors.As(ne, &se) {
|
||||
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
|
||||
brokenPipe = true
|
||||
}
|
||||
@ -165,7 +167,7 @@ func function(pc uintptr) []byte {
|
||||
return name
|
||||
}
|
||||
|
||||
// timeFormat returns a customized time string for logger.
|
||||
func timeFormat(t time.Time) string {
|
||||
timeString := t.Format("2006/01/02 - 15:04:05")
|
||||
return timeString
|
||||
return t.Format("2006/01/02 - 15:04:05")
|
||||
}
|
||||
|
@ -92,14 +92,14 @@ func TestPanicWithAbort(t *testing.T) {
|
||||
|
||||
func TestSource(t *testing.T) {
|
||||
bs := source(nil, 0)
|
||||
assert.Equal(t, []byte("???"), bs)
|
||||
assert.Equal(t, dunno, bs)
|
||||
|
||||
in := [][]byte{
|
||||
[]byte("Hello world."),
|
||||
[]byte("Hi, gin.."),
|
||||
}
|
||||
bs = source(in, 10)
|
||||
assert.Equal(t, []byte("???"), bs)
|
||||
assert.Equal(t, dunno, bs)
|
||||
|
||||
bs = source(in, 1)
|
||||
assert.Equal(t, []byte("Hello world."), bs)
|
||||
@ -107,7 +107,7 @@ func TestSource(t *testing.T) {
|
||||
|
||||
func TestFunction(t *testing.T) {
|
||||
bs := function(1)
|
||||
assert.Equal(t, []byte("???"), bs)
|
||||
assert.Equal(t, dunno, bs)
|
||||
}
|
||||
|
||||
// TestPanicWithBrokenPipe asserts that recovery specifically handles
|
||||
|
@ -49,7 +49,7 @@ type PureJSON struct {
|
||||
var (
|
||||
jsonContentType = []string{"application/json; charset=utf-8"}
|
||||
jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
||||
jsonAsciiContentType = []string{"application/json"}
|
||||
jsonASCIIContentType = []string{"application/json"}
|
||||
)
|
||||
|
||||
// Render (JSON) writes data with custom ContentType.
|
||||
@ -102,8 +102,7 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||
// if the jsonBytes is array values
|
||||
if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
|
||||
bytesconv.StringToBytes("]")) {
|
||||
_, err = w.Write(bytesconv.StringToBytes(r.Prefix))
|
||||
if err != nil {
|
||||
if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -130,20 +129,19 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
||||
}
|
||||
|
||||
callback := template.JSEscapeString(r.Callback)
|
||||
_, err = w.Write(bytesconv.StringToBytes(callback))
|
||||
if err != nil {
|
||||
if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(bytesconv.StringToBytes("("))
|
||||
if err != nil {
|
||||
|
||||
if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(ret)
|
||||
if err != nil {
|
||||
|
||||
if _, err = w.Write(ret); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(bytesconv.StringToBytes(");"))
|
||||
if err != nil {
|
||||
|
||||
if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -178,7 +176,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
||||
|
||||
// WriteContentType (AsciiJSON) writes JSON ContentType.
|
||||
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, jsonAsciiContentType)
|
||||
writeContentType(w, jsonASCIIContentType)
|
||||
}
|
||||
|
||||
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
||||
|
@ -13,6 +13,8 @@ import (
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
// Check interface implemented here to support go build tag nomsgpack.
|
||||
// See: https://github.com/gin-gonic/gin/pull/1852/
|
||||
var (
|
||||
_ Render = MsgPack{}
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ package render
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ProtoBuf contains the given interface object.
|
||||
|
@ -39,6 +39,6 @@ func TestRenderMsgPack(t *testing.T) {
|
||||
err = codec.NewEncoder(buf, h).Encode(data)
|
||||
|
||||
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"))
|
||||
}
|
||||
|
@ -14,10 +14,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
testdata "github.com/gin-gonic/gin/testdata/protoexample"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// TODO unit tests
|
||||
@ -420,7 +419,8 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
|
||||
|
||||
func TestRenderHTMLDebugFiles(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
htmlRender := HTMLDebug{Files: []string{"../testdata/template/hello.tmpl"},
|
||||
htmlRender := HTMLDebug{
|
||||
Files: []string{"../testdata/template/hello.tmpl"},
|
||||
Glob: "",
|
||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||
FuncMap: nil,
|
||||
@ -438,7 +438,8 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
|
||||
|
||||
func TestRenderHTMLDebugGlob(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
htmlRender := HTMLDebug{Files: nil,
|
||||
htmlRender := HTMLDebug{
|
||||
Files: nil,
|
||||
Glob: "../testdata/template/hello*",
|
||||
Delims: Delims{Left: "{[{", Right: "}]}"},
|
||||
FuncMap: nil,
|
||||
@ -455,7 +456,8 @@ func TestRenderHTMLDebugGlob(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderHTMLDebugPanics(t *testing.T) {
|
||||
htmlRender := HTMLDebug{Files: nil,
|
||||
htmlRender := HTMLDebug{
|
||||
Files: nil,
|
||||
Glob: "",
|
||||
Delims: Delims{"{{", "}}"},
|
||||
FuncMap: nil,
|
||||
|
@ -17,12 +17,14 @@ import (
|
||||
// func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
// func (w *responseWriter) Flush() {
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
var _ http.ResponseWriter = &responseWriter{}
|
||||
var _ http.ResponseWriter = ResponseWriter(&responseWriter{})
|
||||
var _ http.Hijacker = ResponseWriter(&responseWriter{})
|
||||
var _ http.Flusher = ResponseWriter(&responseWriter{})
|
||||
var _ http.CloseNotifier = ResponseWriter(&responseWriter{})
|
||||
var (
|
||||
_ ResponseWriter = &responseWriter{}
|
||||
_ http.ResponseWriter = &responseWriter{}
|
||||
_ http.ResponseWriter = ResponseWriter(&responseWriter{})
|
||||
_ http.Hijacker = ResponseWriter(&responseWriter{})
|
||||
_ http.Flusher = ResponseWriter(&responseWriter{})
|
||||
_ http.CloseNotifier = ResponseWriter(&responseWriter{})
|
||||
)
|
||||
|
||||
func init() {
|
||||
SetMode(TestMode)
|
||||
|
@ -14,6 +14,13 @@ import (
|
||||
var (
|
||||
// reg match english letters for http method name
|
||||
regEnLetter = regexp.MustCompile("^[A-Z]+$")
|
||||
|
||||
// anyMethods for RouterGroup Any method
|
||||
anyMethods = []string{
|
||||
http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
|
||||
http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
|
||||
http.MethodTrace,
|
||||
}
|
||||
)
|
||||
|
||||
// IRouter defines all router handle interface includes single and group router.
|
||||
@ -136,15 +143,10 @@ func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRo
|
||||
// Any registers a route that matches all the HTTP methods.
|
||||
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
|
||||
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
group.handle(http.MethodGet, relativePath, handlers)
|
||||
group.handle(http.MethodPost, relativePath, handlers)
|
||||
group.handle(http.MethodPut, relativePath, handlers)
|
||||
group.handle(http.MethodPatch, relativePath, handlers)
|
||||
group.handle(http.MethodHead, relativePath, handlers)
|
||||
group.handle(http.MethodOptions, relativePath, handlers)
|
||||
group.handle(http.MethodDelete, relativePath, handlers)
|
||||
group.handle(http.MethodConnect, relativePath, handlers)
|
||||
group.handle(http.MethodTrace, relativePath, handlers)
|
||||
for _, method := range anyMethods {
|
||||
group.handle(method, relativePath, handlers)
|
||||
}
|
||||
|
||||
return group.returnObj()
|
||||
}
|
||||
|
||||
@ -214,9 +216,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
||||
|
||||
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
|
||||
finalSize := len(group.Handlers) + len(handlers)
|
||||
if finalSize >= int(abortIndex) {
|
||||
panic("too many handlers")
|
||||
}
|
||||
assert1(finalSize < int(abortIndex), "too many handlers")
|
||||
mergedHandlers := make(HandlersChain, finalSize)
|
||||
copy(mergedHandlers, group.Handlers)
|
||||
copy(mergedHandlers[len(group.Handlers):], handlers)
|
||||
|
@ -112,15 +112,19 @@ func TestRouterGroupInvalidStaticFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRouterGroupTooManyHandlers(t *testing.T) {
|
||||
const (
|
||||
panicValue = "too many handlers"
|
||||
maximumCnt = abortIndex
|
||||
)
|
||||
router := New()
|
||||
handlers1 := make([]HandlerFunc, 40)
|
||||
handlers1 := make([]HandlerFunc, maximumCnt-1)
|
||||
router.Use(handlers1...)
|
||||
|
||||
handlers2 := make([]HandlerFunc, 26)
|
||||
assert.Panics(t, func() {
|
||||
handlers2 := make([]HandlerFunc, maximumCnt+1)
|
||||
assert.PanicsWithValue(t, panicValue, func() {
|
||||
router.Use(handlers2...)
|
||||
})
|
||||
assert.Panics(t, func() {
|
||||
assert.PanicsWithValue(t, panicValue, func() {
|
||||
router.GET("/", handlers2...)
|
||||
})
|
||||
}
|
||||
|
@ -481,6 +481,21 @@ func TestRouterNotFound(t *testing.T) {
|
||||
router.GET("/a", func(c *Context) {})
|
||||
w = performRequest(router, http.MethodGet, "/")
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
|
||||
// Reproduction test for the bug of issue #2843
|
||||
router = New()
|
||||
router.NoRoute(func(c *Context) {
|
||||
if c.Request.RequestURI == "/login" {
|
||||
c.String(200, "login")
|
||||
}
|
||||
})
|
||||
router.GET("/logout", func(c *Context) {
|
||||
c.String(200, "logout")
|
||||
})
|
||||
w = performRequest(router, http.MethodGet, "/login")
|
||||
assert.Equal(t, "login", w.Body.String())
|
||||
w = performRequest(router, http.MethodGet, "/logout")
|
||||
assert.Equal(t, "logout", w.Body.String())
|
||||
}
|
||||
|
||||
func TestRouterStaticFSNotFound(t *testing.T) {
|
||||
|
301
testdata/protoexample/test.pb.go
vendored
301
testdata/protoexample/test.pb.go
vendored
@ -1,24 +1,24 @@
|
||||
// Code generated by protoc-gen-go.
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.27.0
|
||||
// protoc v3.15.8
|
||||
// source: test.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package protoexample is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
test.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Test
|
||||
*/
|
||||
package protoexample
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import math "math"
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = math.Inf
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type FOO int32
|
||||
|
||||
@ -26,88 +26,273 @@ const (
|
||||
FOO_X FOO = 17
|
||||
)
|
||||
|
||||
var FOO_name = map[int32]string{
|
||||
17: "X",
|
||||
}
|
||||
var FOO_value = map[string]int32{
|
||||
"X": 17,
|
||||
}
|
||||
// Enum value maps for FOO.
|
||||
var (
|
||||
FOO_name = map[int32]string{
|
||||
17: "X",
|
||||
}
|
||||
FOO_value = map[string]int32{
|
||||
"X": 17,
|
||||
}
|
||||
)
|
||||
|
||||
func (x FOO) Enum() *FOO {
|
||||
p := new(FOO)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x FOO) String() string {
|
||||
return proto.EnumName(FOO_name, int32(x))
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
func (x *FOO) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO")
|
||||
|
||||
func (FOO) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_test_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (FOO) Type() protoreflect.EnumType {
|
||||
return &file_test_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x FOO) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Do not use.
|
||||
func (x *FOO) UnmarshalJSON(b []byte) error {
|
||||
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = FOO(value)
|
||||
*x = FOO(num)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
|
||||
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
|
||||
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
|
||||
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
// Deprecated: Use FOO.Descriptor instead.
|
||||
func (FOO) EnumDescriptor() ([]byte, []int) {
|
||||
return file_test_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (m *Test) Reset() { *m = Test{} }
|
||||
func (m *Test) String() string { return proto.CompactTextString(m) }
|
||||
func (*Test) ProtoMessage() {}
|
||||
type Test struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
const Default_Test_Type int32 = 77
|
||||
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
|
||||
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
|
||||
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
|
||||
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup,json=optionalgroup" json:"optionalgroup,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Test) GetLabel() string {
|
||||
if m != nil && m.Label != nil {
|
||||
return *m.Label
|
||||
// Default values for Test fields.
|
||||
const (
|
||||
Default_Test_Type = int32(77)
|
||||
)
|
||||
|
||||
func (x *Test) Reset() {
|
||||
*x = Test{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_test_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Test) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Test) ProtoMessage() {}
|
||||
|
||||
func (x *Test) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_test_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Test.ProtoReflect.Descriptor instead.
|
||||
func (*Test) Descriptor() ([]byte, []int) {
|
||||
return file_test_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Test) GetLabel() string {
|
||||
if x != nil && x.Label != nil {
|
||||
return *x.Label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Test) GetType() int32 {
|
||||
if m != nil && m.Type != nil {
|
||||
return *m.Type
|
||||
func (x *Test) GetType() int32 {
|
||||
if x != nil && x.Type != nil {
|
||||
return *x.Type
|
||||
}
|
||||
return Default_Test_Type
|
||||
}
|
||||
|
||||
func (m *Test) GetReps() []int64 {
|
||||
if m != nil {
|
||||
return m.Reps
|
||||
func (x *Test) GetReps() []int64 {
|
||||
if x != nil {
|
||||
return x.Reps
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Test) GetOptionalgroup() *Test_OptionalGroup {
|
||||
if m != nil {
|
||||
return m.Optionalgroup
|
||||
func (x *Test) GetOptionalgroup() *Test_OptionalGroup {
|
||||
if x != nil {
|
||||
return x.Optionalgroup
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Test_OptionalGroup struct {
|
||||
RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RequiredField *string `protobuf:"bytes,5,req,name=RequiredField" json:"RequiredField,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} }
|
||||
func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) }
|
||||
func (*Test_OptionalGroup) ProtoMessage() {}
|
||||
func (x *Test_OptionalGroup) Reset() {
|
||||
*x = Test_OptionalGroup{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_test_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Test_OptionalGroup) GetRequiredField() string {
|
||||
if m != nil && m.RequiredField != nil {
|
||||
return *m.RequiredField
|
||||
func (x *Test_OptionalGroup) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Test_OptionalGroup) ProtoMessage() {}
|
||||
|
||||
func (x *Test_OptionalGroup) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_test_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Test_OptionalGroup.ProtoReflect.Descriptor instead.
|
||||
func (*Test_OptionalGroup) Descriptor() ([]byte, []int) {
|
||||
return file_test_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *Test_OptionalGroup) GetRequiredField() string {
|
||||
if x != nil && x.RequiredField != nil {
|
||||
return *x.RequiredField
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("protoexample.FOO", FOO_name, FOO_value)
|
||||
var File_test_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_test_proto_rawDesc = []byte{
|
||||
0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x22, 0xc7, 0x01, 0x0a, 0x04, 0x54,
|
||||
0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x02,
|
||||
0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x04, 0x74, 0x79, 0x70,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x3a, 0x02, 0x37, 0x37, 0x52, 0x04, 0x74, 0x79, 0x70,
|
||||
0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52,
|
||||
0x04, 0x72, 0x65, 0x70, 0x73, 0x12, 0x46, 0x0a, 0x0d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61,
|
||||
0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0a, 0x32, 0x20, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74,
|
||||
0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x0d,
|
||||
0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x35, 0x0a,
|
||||
0x0d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x24,
|
||||
0x0a, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x18,
|
||||
0x05, 0x20, 0x02, 0x28, 0x09, 0x52, 0x0d, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x46,
|
||||
0x69, 0x65, 0x6c, 0x64, 0x2a, 0x0c, 0x0a, 0x03, 0x46, 0x4f, 0x4f, 0x12, 0x05, 0x0a, 0x01, 0x58,
|
||||
0x10, 0x11,
|
||||
}
|
||||
|
||||
var (
|
||||
file_test_proto_rawDescOnce sync.Once
|
||||
file_test_proto_rawDescData = file_test_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_test_proto_rawDescGZIP() []byte {
|
||||
file_test_proto_rawDescOnce.Do(func() {
|
||||
file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
|
||||
})
|
||||
return file_test_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_test_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_test_proto_goTypes = []interface{}{
|
||||
(FOO)(0), // 0: protoexample.FOO
|
||||
(*Test)(nil), // 1: protoexample.Test
|
||||
(*Test_OptionalGroup)(nil), // 2: protoexample.Test.OptionalGroup
|
||||
}
|
||||
var file_test_proto_depIdxs = []int32{
|
||||
2, // 0: protoexample.Test.optionalgroup:type_name -> protoexample.Test.OptionalGroup
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_test_proto_init() }
|
||||
func file_test_proto_init() {
|
||||
if File_test_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Test); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Test_OptionalGroup); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_test_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_test_proto_goTypes,
|
||||
DependencyIndexes: file_test_proto_depIdxs,
|
||||
EnumInfos: file_test_proto_enumTypes,
|
||||
MessageInfos: file_test_proto_msgTypes,
|
||||
}.Build()
|
||||
File_test_proto = out.File
|
||||
file_test_proto_rawDesc = nil
|
||||
file_test_proto_goTypes = nil
|
||||
file_test_proto_depIdxs = nil
|
||||
}
|
||||
|
108
tree.go
108
tree.go
@ -17,6 +17,7 @@ import (
|
||||
var (
|
||||
strColon = []byte(":")
|
||||
strStar = []byte("*")
|
||||
strSlash = []byte("/")
|
||||
)
|
||||
|
||||
// Param is a single URL parameter, consisting of a key and a value.
|
||||
@ -30,8 +31,8 @@ type Param struct {
|
||||
// It is therefore safe to read values by the index.
|
||||
type Params []Param
|
||||
|
||||
// Get returns the value of the first Param which key matches the given name.
|
||||
// If no matching Param is found, an empty string is returned.
|
||||
// Get returns the value of the first Param which key matches the given name and a boolean true.
|
||||
// If no matching Param is found, an empty string is returned and a boolean false .
|
||||
func (ps Params) Get(name string) (string, bool) {
|
||||
for _, entry := range ps {
|
||||
if entry.Key == name {
|
||||
@ -98,11 +99,15 @@ func countParams(path string) uint16 {
|
||||
return n
|
||||
}
|
||||
|
||||
func countSections(path string) uint16 {
|
||||
s := bytesconv.StringToBytes(path)
|
||||
return uint16(bytes.Count(s, strSlash))
|
||||
}
|
||||
|
||||
type nodeType uint8
|
||||
|
||||
const (
|
||||
static nodeType = iota // default
|
||||
root
|
||||
root nodeType = iota + 1
|
||||
param
|
||||
catchAll
|
||||
)
|
||||
@ -118,11 +123,6 @@ 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
|
||||
@ -399,13 +399,19 @@ type nodeValue struct {
|
||||
fullPath string
|
||||
}
|
||||
|
||||
type skippedNode struct {
|
||||
path string
|
||||
node *node
|
||||
paramsCount int16
|
||||
}
|
||||
|
||||
// Returns the handle registered with the given path (key). The values of
|
||||
// wildcards are saved to a map.
|
||||
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
||||
// 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
|
||||
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
|
||||
var globalParamsCount int16
|
||||
|
||||
walk: // Outer loop for walking the tree
|
||||
for {
|
||||
@ -418,10 +424,13 @@ 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{
|
||||
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
|
||||
if n.wildChild {
|
||||
index := len(*skippedNodes)
|
||||
*skippedNodes = (*skippedNodes)[:index+1]
|
||||
(*skippedNodes)[index] = skippedNode{
|
||||
path: prefix + path,
|
||||
paramNode: &node{
|
||||
node: &node{
|
||||
path: n.path,
|
||||
wildChild: n.wildChild,
|
||||
nType: n.nType,
|
||||
@ -430,6 +439,7 @@ walk: // Outer loop for walking the tree
|
||||
handlers: n.handlers,
|
||||
fullPath: n.fullPath,
|
||||
},
|
||||
paramsCount: globalParamsCount,
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,21 +447,43 @@ walk: // Outer loop for walking the tree
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
|
||||
// the current node needs to roll back to last vaild skippedNode
|
||||
|
||||
if path != "/" && !n.wildChild {
|
||||
for l := len(*skippedNodes); l > 0; {
|
||||
skippedNode := (*skippedNodes)[l-1]
|
||||
*skippedNodes = (*skippedNodes)[:l-1]
|
||||
if strings.HasSuffix(skippedNode.path, path) {
|
||||
path = skippedNode.path
|
||||
n = skippedNode.node
|
||||
if value.params != nil {
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no wildcard pattern, recommend a redirection
|
||||
if !n.wildChild {
|
||||
// Nothing found.
|
||||
// We can recommend to redirect to the same URL without a
|
||||
// trailing slash if a leaf exists for that path.
|
||||
value.tsr = (path == "/" && n.handlers != nil)
|
||||
value.tsr = path == "/" && n.handlers != nil
|
||||
return
|
||||
}
|
||||
|
||||
// Handle wildcard child, which is always at the end of the array
|
||||
n = n.children[len(n.children)-1]
|
||||
globalParamsCount++
|
||||
|
||||
switch n.nType {
|
||||
case param:
|
||||
// fix truncate the parameter
|
||||
// tree_test.go line: 204
|
||||
|
||||
// Find param end (either '/' or path end)
|
||||
end := 0
|
||||
for end < len(path) && path[end] != '/' {
|
||||
@ -459,7 +491,7 @@ walk: // Outer loop for walking the tree
|
||||
}
|
||||
|
||||
// Save param value
|
||||
if params != nil {
|
||||
if params != nil && cap(*params) > 0 {
|
||||
if value.params == nil {
|
||||
value.params = params
|
||||
}
|
||||
@ -487,7 +519,7 @@ walk: // Outer loop for walking the tree
|
||||
}
|
||||
|
||||
// ... but we can't
|
||||
value.tsr = (len(path) == end+1)
|
||||
value.tsr = len(path) == end+1
|
||||
return
|
||||
}
|
||||
|
||||
@ -499,7 +531,7 @@ walk: // Outer loop for walking the tree
|
||||
// No handle found. Check if a handle for this path + a
|
||||
// trailing slash exists for TSR recommendation
|
||||
n = n.children[0]
|
||||
value.tsr = (n.path == "/" && n.handlers != nil)
|
||||
value.tsr = n.path == "/" && n.handlers != nil
|
||||
}
|
||||
return
|
||||
|
||||
@ -535,6 +567,24 @@ walk: // Outer loop for walking the tree
|
||||
}
|
||||
|
||||
if path == prefix {
|
||||
// If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
|
||||
// the current node needs to roll back to last vaild skippedNode
|
||||
if n.handlers == nil && path != "/" {
|
||||
for l := len(*skippedNodes); l > 0; {
|
||||
skippedNode := (*skippedNodes)[l-1]
|
||||
*skippedNodes = (*skippedNodes)[:l-1]
|
||||
if strings.HasSuffix(skippedNode.path, path) {
|
||||
path = skippedNode.path
|
||||
n = skippedNode.node
|
||||
if value.params != nil {
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
continue walk
|
||||
}
|
||||
}
|
||||
// n = latestNode.children[len(latestNode.children)-1]
|
||||
}
|
||||
// We should have reached the node containing the handle.
|
||||
// Check if this node has a handle registered.
|
||||
if value.handlers = n.handlers; value.handlers != nil {
|
||||
@ -564,16 +614,26 @@ 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
|
||||
// roll back to last vaild skippedNode
|
||||
if path != "/" {
|
||||
for l := len(*skippedNodes); l > 0; {
|
||||
skippedNode := (*skippedNodes)[l-1]
|
||||
*skippedNodes = (*skippedNodes)[:l-1]
|
||||
if strings.HasSuffix(skippedNode.path, path) {
|
||||
path = skippedNode.path
|
||||
n = skippedNode.node
|
||||
if value.params != nil {
|
||||
*value.params = (*value.params)[:skippedNode.paramsCount]
|
||||
}
|
||||
globalParamsCount = skippedNode.paramsCount
|
||||
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 == "/") ||
|
||||
value.tsr = path == "/" ||
|
||||
(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
|
||||
path == prefix[:len(prefix)-1] && n.handlers != nil)
|
||||
return
|
||||
|
154
tree_test.go
154
tree_test.go
@ -33,6 +33,11 @@ func getParams() *Params {
|
||||
return &ps
|
||||
}
|
||||
|
||||
func getSkippedNodes() *[]skippedNode {
|
||||
ps := make([]skippedNode, 0, 20)
|
||||
return &ps
|
||||
}
|
||||
|
||||
func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ...bool) {
|
||||
unescape := false
|
||||
if len(unescapes) >= 1 {
|
||||
@ -40,7 +45,7 @@ func checkRequests(t *testing.T, tree *node, requests testRequests, unescapes ..
|
||||
}
|
||||
|
||||
for _, request := range requests {
|
||||
value := tree.getValue(request.path, getParams(), unescape)
|
||||
value := tree.getValue(request.path, getParams(), getSkippedNodes(), unescape)
|
||||
|
||||
if value.handlers == nil {
|
||||
if !request.nilHandler {
|
||||
@ -154,6 +159,39 @@ func TestTreeWildcard(t *testing.T) {
|
||||
"/info/:user/public",
|
||||
"/info/:user/project/:project",
|
||||
"/info/:user/project/golang",
|
||||
"/aa/*xx",
|
||||
"/ab/*xx",
|
||||
"/:cc",
|
||||
"/c1/:dd/e",
|
||||
"/c1/:dd/e1",
|
||||
"/:cc/cc",
|
||||
"/:cc/:dd/ee",
|
||||
"/:cc/:dd/:ee/ff",
|
||||
"/:cc/:dd/:ee/:ff/gg",
|
||||
"/:cc/:dd/:ee/:ff/:gg/hh",
|
||||
"/get/test/abc/",
|
||||
"/get/:param/abc/",
|
||||
"/something/:paramname/thirdthing",
|
||||
"/something/secondthing/test",
|
||||
"/get/abc",
|
||||
"/get/:param",
|
||||
"/get/abc/123abc",
|
||||
"/get/abc/:param",
|
||||
"/get/abc/123abc/xxx8",
|
||||
"/get/abc/123abc/:param",
|
||||
"/get/abc/123abc/xxx8/1234",
|
||||
"/get/abc/123abc/xxx8/:param",
|
||||
"/get/abc/123abc/xxx8/1234/ffas",
|
||||
"/get/abc/123abc/xxx8/1234/:param",
|
||||
"/get/abc/123abc/xxx8/1234/kkdd/12c",
|
||||
"/get/abc/123abc/xxx8/1234/kkdd/:param",
|
||||
"/get/abc/:param/test",
|
||||
"/get/abc/123abd/:param",
|
||||
"/get/abc/123abddd/:param",
|
||||
"/get/abc/123/:param",
|
||||
"/get/abc/123abg/:param",
|
||||
"/get/abc/123abf/:param",
|
||||
"/get/abc/123abfff/:param",
|
||||
}
|
||||
for _, route := range routes {
|
||||
tree.addRoute(route, fakeHandler(route))
|
||||
@ -186,6 +224,97 @@ func TestTreeWildcard(t *testing.T) {
|
||||
{"/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"}}},
|
||||
{"/aa/aa", false, "/aa/*xx", Params{Param{Key: "xx", Value: "/aa"}}},
|
||||
{"/ab/ab", false, "/ab/*xx", Params{Param{Key: "xx", Value: "/ab"}}},
|
||||
{"/a", false, "/:cc", Params{Param{Key: "cc", Value: "a"}}},
|
||||
// * Error with argument being intercepted
|
||||
// new PR handle (/all /all/cc /a/cc)
|
||||
// fix PR: https://github.com/gin-gonic/gin/pull/2796
|
||||
{"/all", false, "/:cc", Params{Param{Key: "cc", Value: "all"}}},
|
||||
{"/d", false, "/:cc", Params{Param{Key: "cc", Value: "d"}}},
|
||||
{"/ad", false, "/:cc", Params{Param{Key: "cc", Value: "ad"}}},
|
||||
{"/dd", false, "/:cc", Params{Param{Key: "cc", Value: "dd"}}},
|
||||
{"/dddaa", false, "/:cc", Params{Param{Key: "cc", Value: "dddaa"}}},
|
||||
{"/aa", false, "/:cc", Params{Param{Key: "cc", Value: "aa"}}},
|
||||
{"/aaa", false, "/:cc", Params{Param{Key: "cc", Value: "aaa"}}},
|
||||
{"/aaa/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "aaa"}}},
|
||||
{"/ab", false, "/:cc", Params{Param{Key: "cc", Value: "ab"}}},
|
||||
{"/abb", false, "/:cc", Params{Param{Key: "cc", Value: "abb"}}},
|
||||
{"/abb/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "abb"}}},
|
||||
{"/allxxxx", false, "/:cc", Params{Param{Key: "cc", Value: "allxxxx"}}},
|
||||
{"/alldd", false, "/:cc", Params{Param{Key: "cc", Value: "alldd"}}},
|
||||
{"/all/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "all"}}},
|
||||
{"/a/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "a"}}},
|
||||
{"/c1/d/e", false, "/c1/:dd/e", Params{Param{Key: "dd", Value: "d"}}},
|
||||
{"/c1/d/e1", false, "/c1/:dd/e1", Params{Param{Key: "dd", Value: "d"}}},
|
||||
{"/c1/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c1"}, Param{Key: "dd", Value: "d"}}},
|
||||
{"/cc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "cc"}}},
|
||||
{"/ccc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "ccc"}}},
|
||||
{"/deedwjfs/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "deedwjfs"}}},
|
||||
{"/acllcc/cc", false, "/:cc/cc", Params{Param{Key: "cc", Value: "acllcc"}}},
|
||||
{"/get/test/abc/", false, "/get/test/abc/", nil},
|
||||
{"/get/te/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "te"}}},
|
||||
{"/get/testaa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "testaa"}}},
|
||||
{"/get/xx/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "xx"}}},
|
||||
{"/get/tt/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "tt"}}},
|
||||
{"/get/a/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "a"}}},
|
||||
{"/get/t/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "t"}}},
|
||||
{"/get/aa/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "aa"}}},
|
||||
{"/get/abas/abc/", false, "/get/:param/abc/", Params{Param{Key: "param", Value: "abas"}}},
|
||||
{"/something/secondthing/test", false, "/something/secondthing/test", nil},
|
||||
{"/something/abcdad/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "abcdad"}}},
|
||||
{"/something/secondthingaaaa/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "secondthingaaaa"}}},
|
||||
{"/something/se/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "se"}}},
|
||||
{"/something/s/thirdthing", false, "/something/:paramname/thirdthing", Params{Param{Key: "paramname", Value: "s"}}},
|
||||
{"/c/d/ee", false, "/:cc/:dd/ee", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}}},
|
||||
{"/c/d/e/ff", false, "/:cc/:dd/:ee/ff", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}}},
|
||||
{"/c/d/e/f/gg", false, "/:cc/:dd/:ee/:ff/gg", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}}},
|
||||
{"/c/d/e/f/g/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "c"}, Param{Key: "dd", Value: "d"}, Param{Key: "ee", Value: "e"}, Param{Key: "ff", Value: "f"}, Param{Key: "gg", Value: "g"}}},
|
||||
{"/cc/dd/ee/ff/gg/hh", false, "/:cc/:dd/:ee/:ff/:gg/hh", Params{Param{Key: "cc", Value: "cc"}, Param{Key: "dd", Value: "dd"}, Param{Key: "ee", Value: "ee"}, Param{Key: "ff", Value: "ff"}, Param{Key: "gg", Value: "gg"}}},
|
||||
{"/get/abc", false, "/get/abc", nil},
|
||||
{"/get/a", false, "/get/:param", Params{Param{Key: "param", Value: "a"}}},
|
||||
{"/get/abz", false, "/get/:param", Params{Param{Key: "param", Value: "abz"}}},
|
||||
{"/get/12a", false, "/get/:param", Params{Param{Key: "param", Value: "12a"}}},
|
||||
{"/get/abcd", false, "/get/:param", Params{Param{Key: "param", Value: "abcd"}}},
|
||||
{"/get/abc/123abc", false, "/get/abc/123abc", nil},
|
||||
{"/get/abc/12", false, "/get/abc/:param", Params{Param{Key: "param", Value: "12"}}},
|
||||
{"/get/abc/123ab", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123ab"}}},
|
||||
{"/get/abc/xyz", false, "/get/abc/:param", Params{Param{Key: "param", Value: "xyz"}}},
|
||||
{"/get/abc/123abcddxx", false, "/get/abc/:param", Params{Param{Key: "param", Value: "123abcddxx"}}},
|
||||
{"/get/abc/123abc/xxx8", false, "/get/abc/123abc/xxx8", nil},
|
||||
{"/get/abc/123abc/x", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "x"}}},
|
||||
{"/get/abc/123abc/xxx", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx"}}},
|
||||
{"/get/abc/123abc/abc", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "abc"}}},
|
||||
{"/get/abc/123abc/xxx8xxas", false, "/get/abc/123abc/:param", Params{Param{Key: "param", Value: "xxx8xxas"}}},
|
||||
{"/get/abc/123abc/xxx8/1234", false, "/get/abc/123abc/xxx8/1234", nil},
|
||||
{"/get/abc/123abc/xxx8/1", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1"}}},
|
||||
{"/get/abc/123abc/xxx8/123", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "123"}}},
|
||||
{"/get/abc/123abc/xxx8/78k", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "78k"}}},
|
||||
{"/get/abc/123abc/xxx8/1234xxxd", false, "/get/abc/123abc/xxx8/:param", Params{Param{Key: "param", Value: "1234xxxd"}}},
|
||||
{"/get/abc/123abc/xxx8/1234/ffas", false, "/get/abc/123abc/xxx8/1234/ffas", nil},
|
||||
{"/get/abc/123abc/xxx8/1234/f", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "f"}}},
|
||||
{"/get/abc/123abc/xxx8/1234/ffa", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffa"}}},
|
||||
{"/get/abc/123abc/xxx8/1234/kka", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "kka"}}},
|
||||
{"/get/abc/123abc/xxx8/1234/ffas321", false, "/get/abc/123abc/xxx8/1234/:param", Params{Param{Key: "param", Value: "ffas321"}}},
|
||||
{"/get/abc/123abc/xxx8/1234/kkdd/12c", false, "/get/abc/123abc/xxx8/1234/kkdd/12c", nil},
|
||||
{"/get/abc/123abc/xxx8/1234/kkdd/1", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "1"}}},
|
||||
{"/get/abc/123abc/xxx8/1234/kkdd/12", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12"}}},
|
||||
{"/get/abc/123abc/xxx8/1234/kkdd/12b", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12b"}}},
|
||||
{"/get/abc/123abc/xxx8/1234/kkdd/34", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "34"}}},
|
||||
{"/get/abc/123abc/xxx8/1234/kkdd/12c2e3", false, "/get/abc/123abc/xxx8/1234/kkdd/:param", Params{Param{Key: "param", Value: "12c2e3"}}},
|
||||
{"/get/abc/12/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "12"}}},
|
||||
{"/get/abc/123abdd/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdd"}}},
|
||||
{"/get/abc/123abdddf/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abdddf"}}},
|
||||
{"/get/abc/123ab/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123ab"}}},
|
||||
{"/get/abc/123abgg/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abgg"}}},
|
||||
{"/get/abc/123abff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abff"}}},
|
||||
{"/get/abc/123abffff/test", false, "/get/abc/:param/test", Params{Param{Key: "param", Value: "123abffff"}}},
|
||||
{"/get/abc/123abd/test", false, "/get/abc/123abd/:param", Params{Param{Key: "param", Value: "test"}}},
|
||||
{"/get/abc/123abddd/test", false, "/get/abc/123abddd/:param", Params{Param{Key: "param", Value: "test"}}},
|
||||
{"/get/abc/123/test22", false, "/get/abc/123/:param", Params{Param{Key: "param", Value: "test22"}}},
|
||||
{"/get/abc/123abg/test", false, "/get/abc/123abg/:param", Params{Param{Key: "param", Value: "test"}}},
|
||||
{"/get/abc/123abf/testss", false, "/get/abc/123abf/:param", Params{Param{Key: "param", Value: "testss"}}},
|
||||
{"/get/abc/123abfff/te", false, "/get/abc/123abfff/:param", Params{Param{Key: "param", Value: "te"}}},
|
||||
})
|
||||
|
||||
checkPriorities(t, tree)
|
||||
@ -318,7 +447,7 @@ func TestTreeChildConflict(t *testing.T) {
|
||||
testRoutes(t, routes)
|
||||
}
|
||||
|
||||
func TestTreeDupliatePath(t *testing.T) {
|
||||
func TestTreeDuplicatePath(t *testing.T) {
|
||||
tree := &node{}
|
||||
|
||||
routes := [...]string{
|
||||
@ -486,7 +615,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
||||
"/doc/",
|
||||
}
|
||||
for _, route := range tsrRoutes {
|
||||
value := tree.getValue(route, nil, false)
|
||||
value := tree.getValue(route, nil, getSkippedNodes(), false)
|
||||
if value.handlers != nil {
|
||||
t.Fatalf("non-nil handler for TSR route '%s", route)
|
||||
} else if !value.tsr {
|
||||
@ -503,7 +632,7 @@ func TestTreeTrailingSlashRedirect(t *testing.T) {
|
||||
"/api/world/abc",
|
||||
}
|
||||
for _, route := range noTsrRoutes {
|
||||
value := tree.getValue(route, nil, false)
|
||||
value := tree.getValue(route, nil, getSkippedNodes(), false)
|
||||
if value.handlers != nil {
|
||||
t.Fatalf("non-nil handler for No-TSR route '%s", route)
|
||||
} else if value.tsr {
|
||||
@ -522,7 +651,7 @@ func TestTreeRootTrailingSlashRedirect(t *testing.T) {
|
||||
t.Fatalf("panic inserting test route: %v", recv)
|
||||
}
|
||||
|
||||
value := tree.getValue("/", nil, false)
|
||||
value := tree.getValue("/", nil, getSkippedNodes(), false)
|
||||
if value.handlers != nil {
|
||||
t.Fatalf("non-nil handler")
|
||||
} else if value.tsr {
|
||||
@ -702,7 +831,7 @@ func TestTreeInvalidNodeType(t *testing.T) {
|
||||
|
||||
// normal lookup
|
||||
recv := catchPanic(func() {
|
||||
tree.getValue("/test", nil, false)
|
||||
tree.getValue("/test", nil, getSkippedNodes(), false)
|
||||
})
|
||||
if rs, ok := recv.(string); !ok || rs != panicMsg {
|
||||
t.Fatalf("Expected panic '"+panicMsg+"', got '%v'", recv)
|
||||
@ -717,6 +846,19 @@ func TestTreeInvalidNodeType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeInvalidParamsType(t *testing.T) {
|
||||
tree := &node{}
|
||||
tree.wildChild = true
|
||||
tree.children = append(tree.children, &node{})
|
||||
tree.children[0].nType = 2
|
||||
|
||||
// set invalid Params type
|
||||
params := make(Params, 0)
|
||||
|
||||
// try to trigger slice bounds out of range with capacity 0
|
||||
tree.getValue("/test", ¶ms, getSkippedNodes(), false)
|
||||
}
|
||||
|
||||
func TestTreeWildcardConflictEx(t *testing.T) {
|
||||
conflicts := [...]struct {
|
||||
route string
|
||||
|
@ -5,4 +5,4 @@
|
||||
package gin
|
||||
|
||||
// Version is the current gin framework's version.
|
||||
const Version = "v1.7.2"
|
||||
const Version = "v1.7.3"
|
||||
|
Loading…
x
Reference in New Issue
Block a user