mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-14 12:12:12 +08:00
commit
7f75a693b6
9
.github/workflows/gin.yml
vendored
9
.github/workflows/gin.yml
vendored
@ -22,19 +22,18 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: "go.mod"
|
go-version: "^1"
|
||||||
check-latest: true
|
|
||||||
- name: Setup golangci-lint
|
- name: Setup golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v4
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: v1.56.2
|
version: v1.58.1
|
||||||
args: --verbose
|
args: --verbose
|
||||||
test:
|
test:
|
||||||
needs: lint
|
needs: lint
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest]
|
os: [ubuntu-latest, macos-latest]
|
||||||
go: ["1.18", "1.19", "1.20", "1.21", "1.22"]
|
go: ["1.21", "1.22"]
|
||||||
test-tags:
|
test-tags:
|
||||||
["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"]
|
["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"]
|
||||||
include:
|
include:
|
||||||
|
@ -55,3 +55,6 @@ issues:
|
|||||||
- linters:
|
- linters:
|
||||||
- revive
|
- revive
|
||||||
path: _test\.go
|
path: _test\.go
|
||||||
|
- path: gin.go
|
||||||
|
linters:
|
||||||
|
- gci
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
project_name: gin
|
project_name: gin
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
-
|
- # If true, skip the build.
|
||||||
# If true, skip the build.
|
|
||||||
# Useful for library projects.
|
# Useful for library projects.
|
||||||
# Default is false
|
# Default is false
|
||||||
skip: true
|
skip: true
|
||||||
@ -10,7 +9,7 @@ builds:
|
|||||||
changelog:
|
changelog:
|
||||||
# Set it to true if you wish to skip the changelog generation.
|
# Set it to true if you wish to skip the changelog generation.
|
||||||
# This may result in an empty release notes on GitHub/GitLab/Gitea.
|
# This may result in an empty release notes on GitHub/GitLab/Gitea.
|
||||||
skip: false
|
disable: false
|
||||||
|
|
||||||
# Changelog generation implementation to use.
|
# Changelog generation implementation to use.
|
||||||
#
|
#
|
||||||
@ -21,7 +20,7 @@ changelog:
|
|||||||
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
|
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
|
||||||
#
|
#
|
||||||
# Defaults to `git`.
|
# Defaults to `git`.
|
||||||
use: git
|
use: github
|
||||||
|
|
||||||
# Sorts the changelog by the commit's messages.
|
# Sorts the changelog by the commit's messages.
|
||||||
# Could either be asc, desc or empty
|
# Could either be asc, desc or empty
|
||||||
@ -38,20 +37,20 @@ changelog:
|
|||||||
- title: Features
|
- title: Features
|
||||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||||
order: 0
|
order: 0
|
||||||
- title: 'Bug fixes'
|
- title: "Bug fixes"
|
||||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||||
order: 1
|
order: 1
|
||||||
- title: 'Enhancements'
|
- title: "Enhancements"
|
||||||
regexp: "^.*chore[(\\w)]*:+.*$"
|
regexp: "^.*chore[(\\w)]*:+.*$"
|
||||||
order: 2
|
order: 2
|
||||||
|
- title: "Refactor"
|
||||||
|
regexp: "^.*refactor[(\\w)]*:+.*$"
|
||||||
|
order: 3
|
||||||
|
- title: "Build process updates"
|
||||||
|
regexp: ^.*?(build|ci)(\(.+\))??!?:.+$
|
||||||
|
order: 4
|
||||||
|
- title: "Documentation updates"
|
||||||
|
regexp: ^.*?docs?(\(.+\))??!?:.+$
|
||||||
|
order: 4
|
||||||
- title: Others
|
- title: Others
|
||||||
order: 999
|
order: 999
|
||||||
|
|
||||||
filters:
|
|
||||||
# Commit messages matching the regexp listed here will be removed from
|
|
||||||
# the changelog
|
|
||||||
# Default is empty
|
|
||||||
exclude:
|
|
||||||
- '^docs'
|
|
||||||
- 'CICD'
|
|
||||||
- typo
|
|
||||||
|
76
CHANGELOG.md
76
CHANGELOG.md
@ -1,8 +1,82 @@
|
|||||||
# Gin ChangeLog
|
# Gin ChangeLog
|
||||||
|
|
||||||
|
## Gin v1.10.0
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* feat(auth): add proxy-server authentication (#3877) (@EndlessParadox1)
|
||||||
|
* feat(bind): ShouldBindBodyWith shortcut and change doc (#3871) (@RedCrazyGhost)
|
||||||
|
* feat(binding): Support custom BindUnmarshaler for binding. (#3933) (@dkkb)
|
||||||
|
* feat(binding): support override default binding implement (#3514) (@ssfyn)
|
||||||
|
* feat(engine): Added `OptionFunc` and `With` (#3572) (@flc1125)
|
||||||
|
* feat(logger): ability to skip logs based on user-defined logic (#3593) (@palvaneh)
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
* Revert "fix(uri): query binding bug (#3236)" (#3899) (@appleboy)
|
||||||
|
* fix(binding): binding error while not upload file (#3819) (#3820) (@clearcodecn)
|
||||||
|
* fix(binding): dereference pointer to struct (#3199) (@echovl)
|
||||||
|
* fix(context): make context Value method adhere to Go standards (#3897) (@FarmerChillax)
|
||||||
|
* fix(engine): fix unit test (#3878) (@flc1125)
|
||||||
|
* fix(header): Allow header according to RFC 7231 (HTTP 405) (#3759) (@Crocmagnon)
|
||||||
|
* fix(route): Add fullPath in context copy (#3784) (@KarthikReddyPuli)
|
||||||
|
* fix(router): catch-all conflicting wildcard (#3812) (@FirePing32)
|
||||||
|
* fix(sec): upgrade golang.org/x/crypto to 0.17.0 (#3832) (@chncaption)
|
||||||
|
* fix(tree): correctly expand the capacity of params (#3502) (@georgijd-form3)
|
||||||
|
* fix(uri): query binding bug (#3236) (@illiafox)
|
||||||
|
* fix: Add pointer support for url query params (#3659) (#3666) (@omkar-foss)
|
||||||
|
* fix: protect Context.Keys map when call Copy method (#3873) (@kingcanfish)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* chore(CI): update release args (#3595) (@qloog)
|
||||||
|
* chore(IP): add TrustedPlatform constant for Fly.io. (#3839) (@ab)
|
||||||
|
* chore(debug): add ability to override the debugPrint statement (#2337) (@josegonzalez)
|
||||||
|
* chore(deps): update dependencies to latest versions (#3835) (@appleboy)
|
||||||
|
* chore(header): Add support for RFC 9512: application/yaml (#3851) (@vincentbernat)
|
||||||
|
* chore(http): use white color for HTTP 1XX (#3741) (@viralparmarme)
|
||||||
|
* chore(optimize): the ShouldBindUri method of the Context struct (#3911) (@1911860538)
|
||||||
|
* chore(perf): Optimize the Copy method of the Context struct (#3859) (@1911860538)
|
||||||
|
* chore(refactor): modify interface check way (#3855) (@demoManito)
|
||||||
|
* chore(request): check reader if it's nil before reading (#3419) (@noahyao1024)
|
||||||
|
* chore(security): upgrade Protobuf for CVE-2024-24786 (#3893) (@Fotkurz)
|
||||||
|
* chore: refactor CI and update dependencies (#3848) (@appleboy)
|
||||||
|
* chore: refactor configuration files for better readability (#3951) (@appleboy)
|
||||||
|
* chore: update GitHub Actions configuration (#3792) (@appleboy)
|
||||||
|
* chore: update changelog categories and improve documentation (#3917) (@appleboy)
|
||||||
|
* chore: update dependencies to latest versions (#3694) (@appleboy)
|
||||||
|
* chore: update external dependencies to latest versions (#3950) (@appleboy)
|
||||||
|
* chore: update various Go dependencies to latest versions (#3901) (@appleboy)
|
||||||
|
|
||||||
|
### Build process updates
|
||||||
|
|
||||||
|
* build(codecov): Added a codecov configuration (#3891) (@flc1125)
|
||||||
|
* ci(Makefile): vet command add .PHONY (#3915) (@imalasong)
|
||||||
|
* ci(lint): update tooling and workflows for consistency (#3834) (@appleboy)
|
||||||
|
* ci(release): refactor changelog regex patterns and exclusions (#3914) (@appleboy)
|
||||||
|
* ci(testing): add go1.22 version (#3842) (@appleboy)
|
||||||
|
|
||||||
|
### Documentation updates
|
||||||
|
|
||||||
|
* docs(context): Added deprecation comments to BindWith (#3880) (@flc1125)
|
||||||
|
* docs(middleware): comments to function `BasicAuthForProxy` (#3881) (@EndlessParadox1)
|
||||||
|
* docs: Add document to constant `AuthProxyUserKey` and `BasicAuthForProxy`. (#3887) (@EndlessParadox1)
|
||||||
|
* docs: fix typo in comment (#3868) (@testwill)
|
||||||
|
* docs: fix typo in function documentation (#3872) (@TotomiEcio)
|
||||||
|
* docs: remove redundant comments (#3765) (@WeiTheShinobi)
|
||||||
|
* feat: update version constant to v1.10.0 (#3952) (@appleboy)
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* Upgrade golang.org/x/net -> v0.13.0 (#3684) (@cpcf)
|
||||||
|
* test(git): gitignore add develop tools (#3370) (@demoManito)
|
||||||
|
* test(http): use constant instead of numeric literal (#3863) (@testwill)
|
||||||
|
* test(path): Optimize unit test execution results (#3883) (@flc1125)
|
||||||
|
* test(render): increased unit tests coverage (#3691) (@araujo88)
|
||||||
|
|
||||||
## Gin v1.9.1
|
## Gin v1.9.1
|
||||||
|
|
||||||
### BUG FIXES
|
### BUG FIXES
|
||||||
|
|
||||||
* fix Request.Context() checks [#3512](https://github.com/gin-gonic/gin/pull/3512)
|
* fix Request.Context() checks [#3512](https://github.com/gin-gonic/gin/pull/3512)
|
||||||
|
|
||||||
|
43
Makefile
43
Makefile
@ -8,6 +8,7 @@ TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | gr
|
|||||||
TESTTAGS ?= ""
|
TESTTAGS ?= ""
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
# Run tests to verify code functionality.
|
||||||
test:
|
test:
|
||||||
echo "mode: count" > coverage.out
|
echo "mode: count" > coverage.out
|
||||||
for d in $(TESTFOLDER); do \
|
for d in $(TESTFOLDER); do \
|
||||||
@ -30,10 +31,12 @@ test:
|
|||||||
done
|
done
|
||||||
|
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
|
# Ensure consistent code formatting.
|
||||||
fmt:
|
fmt:
|
||||||
$(GOFMT) -w $(GOFILES)
|
$(GOFMT) -w $(GOFILES)
|
||||||
|
|
||||||
.PHONY: fmt-check
|
.PHONY: fmt-check
|
||||||
|
# format (check only).
|
||||||
fmt-check:
|
fmt-check:
|
||||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||||
if [ -n "$$diff" ]; then \
|
if [ -n "$$diff" ]; then \
|
||||||
@ -42,31 +45,37 @@ fmt-check:
|
|||||||
exit 1; \
|
exit 1; \
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
|
.PHONY: vet
|
||||||
|
# Examine packages and report suspicious constructs if any.
|
||||||
vet:
|
vet:
|
||||||
$(GO) vet $(VETPACKAGES)
|
$(GO) vet $(VETPACKAGES)
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
|
# Inspect source code for stylistic errors or potential bugs.
|
||||||
lint:
|
lint:
|
||||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) get -u golang.org/x/lint/golint; \
|
$(GO) get -u golang.org/x/lint/golint; \
|
||||||
fi
|
fi
|
||||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||||
|
|
||||||
.PHONY: misspell-check
|
|
||||||
misspell-check:
|
|
||||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
|
||||||
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
|
||||||
fi
|
|
||||||
misspell -error $(GOFILES)
|
|
||||||
|
|
||||||
.PHONY: misspell
|
.PHONY: misspell
|
||||||
|
# Correct commonly misspelled English words in source code.
|
||||||
misspell:
|
misspell:
|
||||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||||
fi
|
fi
|
||||||
misspell -w $(GOFILES)
|
misspell -w $(GOFILES)
|
||||||
|
|
||||||
|
.PHONY: misspell-check
|
||||||
|
# misspell (check only).
|
||||||
|
misspell-check:
|
||||||
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||||
|
fi
|
||||||
|
misspell -error $(GOFILES)
|
||||||
|
|
||||||
.PHONY: tools
|
.PHONY: tools
|
||||||
|
# Install tools (golint and misspell).
|
||||||
tools:
|
tools:
|
||||||
@if [ $(GO_VERSION) -gt 15 ]; then \
|
@if [ $(GO_VERSION) -gt 15 ]; then \
|
||||||
$(GO) install golang.org/x/lint/golint@latest; \
|
$(GO) install golang.org/x/lint/golint@latest; \
|
||||||
@ -75,3 +84,23 @@ tools:
|
|||||||
$(GO) install golang.org/x/lint/golint; \
|
$(GO) install golang.org/x/lint/golint; \
|
||||||
$(GO) install github.com/client9/misspell/cmd/misspell; \
|
$(GO) install github.com/client9/misspell/cmd/misspell; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
# Help.
|
||||||
|
help:
|
||||||
|
@echo ''
|
||||||
|
@echo 'Usage:'
|
||||||
|
@echo ' make [target]'
|
||||||
|
@echo ''
|
||||||
|
@echo 'Targets:'
|
||||||
|
@awk '/^[a-zA-Z\-\0-9]+:/ { \
|
||||||
|
helpMessage = match(lastLine, /^# (.*)/); \
|
||||||
|
if (helpMessage) { \
|
||||||
|
helpCommand = substr($$1, 0, index($$1, ":")-1); \
|
||||||
|
helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \
|
||||||
|
printf " - \033[36m%-20s\033[0m %s\n", helpCommand, helpMessage; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
{ lastLine = $$0 }' $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
75
README.md
75
README.md
@ -11,46 +11,44 @@
|
|||||||
[](https://github.com/gin-gonic/gin/releases)
|
[](https://github.com/gin-gonic/gin/releases)
|
||||||
[](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin)
|
[](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin)
|
||||||
|
|
||||||
Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter).
|
||||||
|
If you need performance and good productivity, you will love Gin.
|
||||||
|
|
||||||
**The key features of Gin are:**
|
**Gin's key features are:**
|
||||||
|
|
||||||
- Zero allocation router
|
- Zero allocation router
|
||||||
- Fast
|
- Speed
|
||||||
- Middleware support
|
- Middleware support
|
||||||
- Crash-free
|
- Crash-free
|
||||||
- JSON validation
|
- JSON validation
|
||||||
- Routes grouping
|
- Route grouping
|
||||||
- Error management
|
- Error management
|
||||||
- Rendering built-in
|
- Built-in rendering
|
||||||
- Extendable
|
- Extensible
|
||||||
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- **[Go](https://go.dev/)**: any one of the **three latest major** [releases](https://go.dev/doc/devel/release) (we test it with these).
|
Gin requires [Go](https://go.dev/) version [1.21](https://go.dev/doc/devel/release#go1.21.0) or above.
|
||||||
|
|
||||||
### Getting Gin
|
### Getting Gin
|
||||||
|
|
||||||
With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import
|
With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), `go [build|run|test]` automatically fetches the necessary dependencies when you add the import in your code:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
import "github.com/gin-gonic/gin"
|
import "github.com/gin-gonic/gin"
|
||||||
```
|
```
|
||||||
|
|
||||||
to your code, and then `go [build|run|test]` will automatically fetch the necessary dependencies.
|
Alternatively, use `go get`:
|
||||||
|
|
||||||
Otherwise, run the following Go command to install the `gin` package:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ go get -u github.com/gin-gonic/gin
|
go get -u github.com/gin-gonic/gin
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running Gin
|
### Running Gin
|
||||||
|
|
||||||
First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`:
|
A basic example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
@ -72,29 +70,29 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And use the Go command to run the demo:
|
To run the code, use the `go run` command, like:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
# run example.go and visit 0.0.0.0:8080/ping on browser
|
|
||||||
$ go run example.go
|
$ go run example.go
|
||||||
```
|
```
|
||||||
|
|
||||||
### Learn more examples
|
Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response!
|
||||||
|
|
||||||
|
### See more examples
|
||||||
|
|
||||||
#### Quick Start
|
#### Quick Start
|
||||||
|
|
||||||
Learn and practice more examples, please read the [Gin Quick Start](docs/doc.md) which includes API examples and builds tag.
|
Learn and practice with the [Gin Quick Start](docs/doc.md), which includes API examples and builds tag.
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
||||||
A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository.
|
A number of ready-to-run examples demonstrating various use cases of Gin are available in the [Gin examples](https://github.com/gin-gonic/examples) repository.
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package.
|
See the [API documentation on godoc.org](https://godoc.org/github.com/gin-gonic/gin).
|
||||||
|
|
||||||
All documentation is available on the Gin website.
|
The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages:
|
||||||
|
|
||||||
- [English](https://gin-gonic.com/docs/)
|
- [English](https://gin-gonic.com/docs/)
|
||||||
- [简体中文](https://gin-gonic.com/zh-cn/docs/)
|
- [简体中文](https://gin-gonic.com/zh-cn/docs/)
|
||||||
@ -105,15 +103,13 @@ All documentation is available on the Gin website.
|
|||||||
- [Turkish](https://gin-gonic.com/tr/docs/)
|
- [Turkish](https://gin-gonic.com/tr/docs/)
|
||||||
- [Persian](https://gin-gonic.com/fa/docs/)
|
- [Persian](https://gin-gonic.com/fa/docs/)
|
||||||
|
|
||||||
### Articles about Gin
|
### Articles
|
||||||
|
|
||||||
A curated list of awesome Gin framework.
|
|
||||||
|
|
||||||
- [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin)
|
- [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin)
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks details](/BENCHMARKS.md).
|
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks](/BENCHMARKS.md).
|
||||||
|
|
||||||
| Benchmark name | (1) | (2) | (3) | (4) |
|
| Benchmark name | (1) | (2) | (3) | (4) |
|
||||||
| ------------------------------ | ---------:| ---------------:| ------------:| ---------------:|
|
| ------------------------------ | ---------:| ---------------:| ------------:| ---------------:|
|
||||||
@ -153,26 +149,23 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
|
|||||||
- (3): Heap Memory (B/op), lower is better
|
- (3): Heap Memory (B/op), lower is better
|
||||||
- (4): Average Allocations per Repetition (allocs/op), lower is better
|
- (4): Average Allocations per Repetition (allocs/op), lower is better
|
||||||
|
|
||||||
|
## Middleware
|
||||||
## Middlewares
|
|
||||||
|
|
||||||
You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib).
|
You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib).
|
||||||
|
|
||||||
|
## Uses
|
||||||
|
|
||||||
## Users
|
Here are some awesome projects that are using the [Gin](https://github.com/gin-gonic/gin) web framework.
|
||||||
|
|
||||||
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
|
|
||||||
|
|
||||||
* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
|
|
||||||
* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform.
|
|
||||||
* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow.
|
|
||||||
* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares.
|
|
||||||
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
|
||||||
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
|
||||||
|
|
||||||
|
- [gorush](https://github.com/appleboy/gorush): A push notification server.
|
||||||
|
- [fnproject](https://github.com/fnproject/fn): A container native, cloud agnostic serverless platform.
|
||||||
|
- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Google TensorFlow.
|
||||||
|
- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middleware.
|
||||||
|
- [picfit](https://github.com/thoas/picfit): An image resizing server.
|
||||||
|
- [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Gin is the work of hundreds of contributors. We appreciate your help!
|
Gin is the work of hundreds of contributors. We appreciate your help!
|
||||||
|
|
||||||
Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow.
|
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on submitting patches and the contribution workflow.
|
||||||
|
@ -84,6 +84,7 @@ var (
|
|||||||
YAML BindingBody = yamlBinding{}
|
YAML BindingBody = yamlBinding{}
|
||||||
Uri BindingUri = uriBinding{}
|
Uri BindingUri = uriBinding{}
|
||||||
Header Binding = headerBinding{}
|
Header Binding = headerBinding{}
|
||||||
|
Plain BindingBody = plainBinding{}
|
||||||
TOML BindingBody = tomlBinding{}
|
TOML BindingBody = tomlBinding{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -81,6 +81,7 @@ var (
|
|||||||
Uri = uriBinding{}
|
Uri = uriBinding{}
|
||||||
Header = headerBinding{}
|
Header = headerBinding{}
|
||||||
TOML = tomlBinding{}
|
TOML = tomlBinding{}
|
||||||
|
Plain = plainBinding{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default returns the appropriate Binding instance based on the HTTP method
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
|
@ -1342,6 +1342,46 @@ func (h hook) Read([]byte) (int, error) {
|
|||||||
return 0, errors.New("error")
|
return 0, errors.New("error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type failRead struct{}
|
||||||
|
|
||||||
|
func (f *failRead) Read(b []byte) (n int, err error) {
|
||||||
|
return 0, errors.New("my fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *failRead) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlainBinding(t *testing.T) {
|
||||||
|
p := Plain
|
||||||
|
assert.Equal(t, "plain", p.Name())
|
||||||
|
|
||||||
|
var s string
|
||||||
|
req := requestWithBody("POST", "/", "test string")
|
||||||
|
assert.NoError(t, p.Bind(req, &s))
|
||||||
|
assert.Equal(t, s, "test string")
|
||||||
|
|
||||||
|
var bs []byte
|
||||||
|
req = requestWithBody("POST", "/", "test []byte")
|
||||||
|
assert.NoError(t, p.Bind(req, &bs))
|
||||||
|
assert.Equal(t, bs, []byte("test []byte"))
|
||||||
|
|
||||||
|
var i int
|
||||||
|
req = requestWithBody("POST", "/", "test fail")
|
||||||
|
assert.Error(t, p.Bind(req, &i))
|
||||||
|
|
||||||
|
req = requestWithBody("POST", "/", "")
|
||||||
|
req.Body = &failRead{}
|
||||||
|
assert.Error(t, p.Bind(req, &s))
|
||||||
|
|
||||||
|
req = requestWithBody("POST", "/", "")
|
||||||
|
assert.Nil(t, p.Bind(req, nil))
|
||||||
|
|
||||||
|
var ptr *string
|
||||||
|
req = requestWithBody("POST", "/", "")
|
||||||
|
assert.Nil(t, p.Bind(req, ptr))
|
||||||
|
}
|
||||||
|
|
||||||
func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) {
|
||||||
assert.Equal(t, name, b.Name())
|
assert.Equal(t, name, b.Name())
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -22,25 +22,20 @@ type SliceValidationError []error
|
|||||||
|
|
||||||
// Error concatenates all error elements in SliceValidationError into a single string separated by \n.
|
// Error concatenates all error elements in SliceValidationError into a single string separated by \n.
|
||||||
func (err SliceValidationError) Error() string {
|
func (err SliceValidationError) Error() string {
|
||||||
n := len(err)
|
if len(err) == 0 {
|
||||||
switch n {
|
|
||||||
case 0:
|
|
||||||
return ""
|
return ""
|
||||||
default:
|
|
||||||
var b strings.Builder
|
|
||||||
if err[0] != nil {
|
|
||||||
fmt.Fprintf(&b, "[%d]: %s", 0, err[0].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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
for i := 0; i < len(err); i++ {
|
||||||
|
if err[i] != nil {
|
||||||
|
if b.Len() > 0 {
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
b.WriteString("[" + strconv.Itoa(i) + "]: " + err[i].Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ StructValidator = (*defaultValidator)(nil)
|
var _ StructValidator = (*defaultValidator)(nil)
|
||||||
|
@ -12,11 +12,15 @@ import (
|
|||||||
|
|
||||||
func BenchmarkSliceValidationError(b *testing.B) {
|
func BenchmarkSliceValidationError(b *testing.B) {
|
||||||
const size int = 100
|
const size int = 100
|
||||||
|
e := make(SliceValidationError, size)
|
||||||
|
for j := 0; j < size; j++ {
|
||||||
|
e[j] = errors.New(strconv.Itoa(j))
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
e := make(SliceValidationError, size)
|
|
||||||
for j := 0; j < size; j++ {
|
|
||||||
e[j] = errors.New(strconv.Itoa(j))
|
|
||||||
}
|
|
||||||
if len(e.Error()) == 0 {
|
if len(e.Error()) == 0 {
|
||||||
b.Errorf("error")
|
b.Errorf("error")
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,23 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
|
|||||||
return setter.TrySet(value, field, tagValue, setOpt)
|
return setter.TrySet(value, field, tagValue, setOpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
||||||
|
type BindUnmarshaler interface {
|
||||||
|
// UnmarshalParam decodes and assigns a value from an form or query param.
|
||||||
|
UnmarshalParam(param string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// trySetCustom tries to set a custom type value
|
||||||
|
// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true`
|
||||||
|
// to skip the default value setting.
|
||||||
|
func trySetCustom(val string, value reflect.Value) (isSet bool, err error) {
|
||||||
|
switch v := value.Addr().Interface().(type) {
|
||||||
|
case BindUnmarshaler:
|
||||||
|
return true, v.UnmarshalParam(val)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet 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]
|
vs, ok := form[tagValue]
|
||||||
if !ok && !opt.isDefaultExists {
|
if !ok && !opt.isDefaultExists {
|
||||||
@ -176,14 +193,25 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
if !ok {
|
if !ok {
|
||||||
vs = []string{opt.defaultValue}
|
vs = []string{opt.defaultValue}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ok, err = trySetCustom(vs[0], value); ok {
|
||||||
|
return ok, err
|
||||||
|
}
|
||||||
|
|
||||||
return true, setSlice(vs, value, field)
|
return true, setSlice(vs, value, field)
|
||||||
case reflect.Array:
|
case reflect.Array:
|
||||||
if !ok {
|
if !ok {
|
||||||
vs = []string{opt.defaultValue}
|
vs = []string{opt.defaultValue}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ok, err = trySetCustom(vs[0], value); ok {
|
||||||
|
return ok, err
|
||||||
|
}
|
||||||
|
|
||||||
if len(vs) != value.Len() {
|
if len(vs) != value.Len() {
|
||||||
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, setArray(vs, value, field)
|
return true, setArray(vs, value, field)
|
||||||
default:
|
default:
|
||||||
var val string
|
var val string
|
||||||
@ -194,6 +222,9 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
|
|||||||
if len(vs) > 0 {
|
if len(vs) > 0 {
|
||||||
val = vs[0]
|
val = vs[0]
|
||||||
}
|
}
|
||||||
|
if ok, err := trySetCustom(val, value); ok {
|
||||||
|
return ok, err
|
||||||
|
}
|
||||||
return true, setWithProperType(val, value, field)
|
return true, setWithProperType(val, value, field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,11 +408,8 @@ func setTimeDuration(val string, value reflect.Value) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func head(str, sep string) (head string, tail string) {
|
func head(str, sep string) (head string, tail string) {
|
||||||
idx := strings.Index(str, sep)
|
head, tail, _ = strings.Cut(str, sep)
|
||||||
if idx < 0 {
|
return head, tail
|
||||||
return str, ""
|
|
||||||
}
|
|
||||||
return str[:idx], str[idx+len(sep):]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFormMap(ptr any, form map[string][]string) error {
|
func setFormMap(ptr any, form map[string][]string) error {
|
||||||
|
@ -5,8 +5,12 @@
|
|||||||
package binding
|
package binding
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -323,3 +327,185 @@ func TestMappingIgnoredCircularRef(t *testing.T) {
|
|||||||
err := mappingByPtr(&s, formSource{}, "form")
|
err := mappingByPtr(&s, formSource{}, "form")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type customUnmarshalParamHex int
|
||||||
|
|
||||||
|
func (f *customUnmarshalParamHex) UnmarshalParam(param string) error {
|
||||||
|
v, err := strconv.ParseInt(param, 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*f = customUnmarshalParamHex(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomUnmarshalParamHexWithFormTag(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Foo customUnmarshalParamHex `form:"foo"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, 245, s.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomUnmarshalParamHexWithURITag(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
Foo customUnmarshalParamHex `uri:"foo"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"foo": {`f5`}}, "uri")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, 245, s.Foo)
|
||||||
|
}
|
||||||
|
|
||||||
|
type customUnmarshalParamType struct {
|
||||||
|
Protocol string
|
||||||
|
Path string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *customUnmarshalParamType) UnmarshalParam(param string) error {
|
||||||
|
parts := strings.Split(param, ":")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return fmt.Errorf("invalid format")
|
||||||
|
}
|
||||||
|
f.Protocol = parts[0]
|
||||||
|
f.Path = parts[1]
|
||||||
|
f.Name = parts[2]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomStructTypeWithFormTag(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
FileData customUnmarshalParamType `form:"data"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "file", s.FileData.Protocol)
|
||||||
|
assert.EqualValues(t, "/foo", s.FileData.Path)
|
||||||
|
assert.EqualValues(t, "happiness", s.FileData.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomStructTypeWithURITag(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
FileData customUnmarshalParamType `uri:"data"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "file", s.FileData.Protocol)
|
||||||
|
assert.EqualValues(t, "/foo", s.FileData.Path)
|
||||||
|
assert.EqualValues(t, "happiness", s.FileData.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomPointerStructTypeWithFormTag(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
FileData *customUnmarshalParamType `form:"data"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "file", s.FileData.Protocol)
|
||||||
|
assert.EqualValues(t, "/foo", s.FileData.Path)
|
||||||
|
assert.EqualValues(t, "happiness", s.FileData.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomPointerStructTypeWithURITag(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
FileData *customUnmarshalParamType `uri:"data"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"data": {`file:/foo:happiness`}}, "uri")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "file", s.FileData.Protocol)
|
||||||
|
assert.EqualValues(t, "/foo", s.FileData.Path)
|
||||||
|
assert.EqualValues(t, "happiness", s.FileData.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type customPath []string
|
||||||
|
|
||||||
|
func (p *customPath) UnmarshalParam(param string) error {
|
||||||
|
elems := strings.Split(param, "/")
|
||||||
|
n := len(elems)
|
||||||
|
if n < 2 {
|
||||||
|
return fmt.Errorf("invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = elems
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomSliceUri(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
FileData customPath `uri:"path"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "uri")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "bar", s.FileData[0])
|
||||||
|
assert.EqualValues(t, "foo", s.FileData[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomSliceForm(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
FileData customPath `form:"path"`
|
||||||
|
}
|
||||||
|
err := mappingByPtr(&s, formSource{"path": {`bar/foo`}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "bar", s.FileData[0])
|
||||||
|
assert.EqualValues(t, "foo", s.FileData[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectID [12]byte
|
||||||
|
|
||||||
|
func (o *objectID) UnmarshalParam(param string) error {
|
||||||
|
oid, err := convertTo(param)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*o = oid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertTo(s string) (objectID, error) {
|
||||||
|
var nilObjectID objectID
|
||||||
|
if len(s) != 24 {
|
||||||
|
return nilObjectID, fmt.Errorf("invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
var oid [12]byte
|
||||||
|
_, err := hex.Decode(oid[:], []byte(s))
|
||||||
|
if err != nil {
|
||||||
|
return nilObjectID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return oid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomArrayUri(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
FileData objectID `uri:"id"`
|
||||||
|
}
|
||||||
|
val := `664a062ac74a8ad104e0e80f`
|
||||||
|
err := mappingByPtr(&s, formSource{"id": {val}}, "uri")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
expected, _ := convertTo(val)
|
||||||
|
assert.EqualValues(t, expected, s.FileData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMappingCustomArrayForm(t *testing.T) {
|
||||||
|
var s struct {
|
||||||
|
FileData objectID `form:"id"`
|
||||||
|
}
|
||||||
|
val := `664a062ac74a8ad104e0e80f`
|
||||||
|
err := mappingByPtr(&s, formSource{"id": {val}}, "form")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
expected, _ := convertTo(val)
|
||||||
|
assert.EqualValues(t, expected, s.FileData)
|
||||||
|
}
|
||||||
|
56
binding/plain.go
Normal file
56
binding/plain.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type plainBinding struct{}
|
||||||
|
|
||||||
|
func (plainBinding) Name() string {
|
||||||
|
return "plain"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plainBinding) Bind(req *http.Request, obj interface{}) error {
|
||||||
|
all, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodePlain(all, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plainBinding) BindBody(body []byte, obj any) error {
|
||||||
|
return decodePlain(body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodePlain(data []byte, obj any) error {
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(obj)
|
||||||
|
|
||||||
|
for v.Kind() == reflect.Ptr {
|
||||||
|
if v.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() == reflect.String {
|
||||||
|
v.SetString(bytesconv.BytesToString(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := v.Interface().([]byte); ok {
|
||||||
|
v.SetBytes(data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("type (%T) unknown type", v)
|
||||||
|
}
|
30
context.go
30
context.go
@ -34,6 +34,7 @@ const (
|
|||||||
MIMEPOSTForm = binding.MIMEPOSTForm
|
MIMEPOSTForm = binding.MIMEPOSTForm
|
||||||
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
||||||
MIMEYAML = binding.MIMEYAML
|
MIMEYAML = binding.MIMEYAML
|
||||||
|
MIMEYAML2 = binding.MIMEYAML2
|
||||||
MIMETOML = binding.MIMETOML
|
MIMETOML = binding.MIMETOML
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -152,6 +153,9 @@ func (c *Context) HandlerName() string {
|
|||||||
func (c *Context) HandlerNames() []string {
|
func (c *Context) HandlerNames() []string {
|
||||||
hn := make([]string, 0, len(c.handlers))
|
hn := make([]string, 0, len(c.handlers))
|
||||||
for _, val := range c.handlers {
|
for _, val := range c.handlers {
|
||||||
|
if val == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
hn = append(hn, nameOfFunction(val))
|
hn = append(hn, nameOfFunction(val))
|
||||||
}
|
}
|
||||||
return hn
|
return hn
|
||||||
@ -182,6 +186,9 @@ func (c *Context) FullPath() string {
|
|||||||
func (c *Context) Next() {
|
func (c *Context) Next() {
|
||||||
c.index++
|
c.index++
|
||||||
for c.index < int8(len(c.handlers)) {
|
for c.index < int8(len(c.handlers)) {
|
||||||
|
if c.handlers[c.index] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
c.handlers[c.index](c)
|
c.handlers[c.index](c)
|
||||||
c.index++
|
c.index++
|
||||||
}
|
}
|
||||||
@ -468,7 +475,7 @@ func (c *Context) QueryArray(key string) (values []string) {
|
|||||||
|
|
||||||
func (c *Context) initQueryCache() {
|
func (c *Context) initQueryCache() {
|
||||||
if c.queryCache == nil {
|
if c.queryCache == nil {
|
||||||
if c.Request != nil {
|
if c.Request != nil && c.Request.URL != nil {
|
||||||
c.queryCache = c.Request.URL.Query()
|
c.queryCache = c.Request.URL.Query()
|
||||||
} else {
|
} else {
|
||||||
c.queryCache = url.Values{}
|
c.queryCache = url.Values{}
|
||||||
@ -614,7 +621,7 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
|
|||||||
}
|
}
|
||||||
defer src.Close()
|
defer src.Close()
|
||||||
|
|
||||||
if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil {
|
if err = os.MkdirAll(filepath.Dir(dst), 0o750); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -667,6 +674,11 @@ func (c *Context) BindTOML(obj any) error {
|
|||||||
return c.MustBindWith(obj, binding.TOML)
|
return c.MustBindWith(obj, binding.TOML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindPlain is a shortcut for c.MustBindWith(obj, binding.Plain).
|
||||||
|
func (c *Context) BindPlain(obj any) error {
|
||||||
|
return c.MustBindWith(obj, binding.Plain)
|
||||||
|
}
|
||||||
|
|
||||||
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
|
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
|
||||||
func (c *Context) BindHeader(obj any) error {
|
func (c *Context) BindHeader(obj any) error {
|
||||||
return c.MustBindWith(obj, binding.Header)
|
return c.MustBindWith(obj, binding.Header)
|
||||||
@ -732,6 +744,11 @@ func (c *Context) ShouldBindTOML(obj any) error {
|
|||||||
return c.ShouldBindWith(obj, binding.TOML)
|
return c.ShouldBindWith(obj, binding.TOML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain).
|
||||||
|
func (c *Context) ShouldBindPlain(obj any) error {
|
||||||
|
return c.ShouldBindWith(obj, binding.Plain)
|
||||||
|
}
|
||||||
|
|
||||||
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
|
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
|
||||||
func (c *Context) ShouldBindHeader(obj any) error {
|
func (c *Context) ShouldBindHeader(obj any) error {
|
||||||
return c.ShouldBindWith(obj, binding.Header)
|
return c.ShouldBindWith(obj, binding.Header)
|
||||||
@ -739,7 +756,7 @@ func (c *Context) ShouldBindHeader(obj any) error {
|
|||||||
|
|
||||||
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
|
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
|
||||||
func (c *Context) ShouldBindUri(obj any) error {
|
func (c *Context) ShouldBindUri(obj any) error {
|
||||||
m := make(map[string][]string)
|
m := make(map[string][]string, len(c.Params))
|
||||||
for _, v := range c.Params {
|
for _, v := range c.Params {
|
||||||
m[v.Key] = []string{v.Value}
|
m[v.Key] = []string{v.Value}
|
||||||
}
|
}
|
||||||
@ -794,6 +811,11 @@ func (c *Context) ShouldBindBodyWithTOML(obj any) error {
|
|||||||
return c.ShouldBindBodyWith(obj, binding.TOML)
|
return c.ShouldBindBodyWith(obj, binding.TOML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON).
|
||||||
|
func (c *Context) ShouldBindBodyWithPlain(obj any) error {
|
||||||
|
return c.ShouldBindBodyWith(obj, binding.Plain)
|
||||||
|
}
|
||||||
|
|
||||||
// ClientIP implements one best effort algorithm to return the real client IP.
|
// ClientIP implements one best effort algorithm to return the real client IP.
|
||||||
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
|
// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
|
||||||
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
|
// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
|
||||||
@ -1161,7 +1183,7 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
|||||||
data := chooseData(config.XMLData, config.Data)
|
data := chooseData(config.XMLData, config.Data)
|
||||||
c.XML(code, data)
|
c.XML(code, data)
|
||||||
|
|
||||||
case binding.MIMEYAML:
|
case binding.MIMEYAML, binding.MIMEYAML2:
|
||||||
data := chooseData(config.YAMLData, config.Data)
|
data := chooseData(config.YAMLData, config.Data)
|
||||||
c.YAML(code, data)
|
c.YAML(code, data)
|
||||||
|
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
// 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.19
|
|
||||||
|
|
||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestContextFormFileFailed18(t *testing.T) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
mw := multipart.NewWriter(buf)
|
|
||||||
defer func(mw *multipart.Writer) {
|
|
||||||
err := mw.Close()
|
|
||||||
if err != nil {
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
}(mw)
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
// Copyright 2022 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.19
|
|
||||||
|
|
||||||
package gin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestContextFormFileFailed19(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)
|
|
||||||
}
|
|
189
context_test.go
189
context_test.go
@ -90,6 +90,19 @@ func TestContextFormFile(t *testing.T) {
|
|||||||
assert.NoError(t, c.SaveUploadedFile(f, "test"))
|
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) {
|
func TestContextMultipartForm(t *testing.T) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
mw := multipart.NewWriter(buf)
|
mw := multipart.NewWriter(buf)
|
||||||
@ -349,7 +362,7 @@ func TestContextHandlerName(t *testing.T) {
|
|||||||
|
|
||||||
func TestContextHandlerNames(t *testing.T) {
|
func TestContextHandlerNames(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest, func(c *Context) {}, handlerNameTest2}
|
c.handlers = HandlersChain{func(c *Context) {}, nil, handlerNameTest, func(c *Context) {}, handlerNameTest2}
|
||||||
|
|
||||||
names := c.HandlerNames()
|
names := c.HandlerNames()
|
||||||
|
|
||||||
@ -410,6 +423,49 @@ func TestContextQuery(t *testing.T) {
|
|||||||
assert.Empty(t, c.PostForm("foo"))
|
assert.Empty(t, c.PostForm("foo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextInitQueryCache(t *testing.T) {
|
||||||
|
validURL, err := url.Parse("https://github.com/gin-gonic/gin/pull/3969?key=value&otherkey=othervalue")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
testName string
|
||||||
|
testContext *Context
|
||||||
|
expectedQueryCache url.Values
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "queryCache should remain unchanged if already not nil",
|
||||||
|
testContext: &Context{
|
||||||
|
queryCache: url.Values{"a": []string{"b"}},
|
||||||
|
Request: &http.Request{URL: validURL}, // valid request for evidence that values weren't extracted
|
||||||
|
},
|
||||||
|
expectedQueryCache: url.Values{"a": []string{"b"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "queryCache should be empty when Request is nil",
|
||||||
|
testContext: &Context{Request: nil}, // explicit nil for readability
|
||||||
|
expectedQueryCache: url.Values{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "queryCache should be empty when Request.URL is nil",
|
||||||
|
testContext: &Context{Request: &http.Request{URL: nil}}, // explicit nil for readability
|
||||||
|
expectedQueryCache: url.Values{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "queryCache should be populated when it not yet populated and Request + Request.URL are non nil",
|
||||||
|
testContext: &Context{Request: &http.Request{URL: validURL}}, // explicit nil for readability
|
||||||
|
expectedQueryCache: url.Values{"key": []string{"value"}, "otherkey": []string{"othervalue"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.testName, func(t *testing.T) {
|
||||||
|
test.testContext.initQueryCache()
|
||||||
|
assert.Equal(t, test.expectedQueryCache, test.testContext.queryCache)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextDefaultQueryOnEmptyRequest(t *testing.T) {
|
func TestContextDefaultQueryOnEmptyRequest(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder()) // here c.Request == nil
|
c, _ := CreateTestContext(httptest.NewRecorder()) // here c.Request == nil
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
@ -1183,7 +1239,7 @@ func TestContextNegotiationWithJSON(t *testing.T) {
|
|||||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
c.Negotiate(http.StatusOK, Negotiate{
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
Offered: []string{MIMEJSON, MIMEXML, MIMEYAML},
|
Offered: []string{MIMEJSON, MIMEXML, MIMEYAML, MIMEYAML2},
|
||||||
Data: H{"foo": "bar"},
|
Data: H{"foo": "bar"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1198,7 +1254,7 @@ func TestContextNegotiationWithXML(t *testing.T) {
|
|||||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
c.Negotiate(http.StatusOK, Negotiate{
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
Offered: []string{MIMEXML, MIMEJSON, MIMEYAML},
|
Offered: []string{MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2},
|
||||||
Data: H{"foo": "bar"},
|
Data: H{"foo": "bar"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1213,7 +1269,7 @@ func TestContextNegotiationWithYAML(t *testing.T) {
|
|||||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
c.Negotiate(http.StatusOK, Negotiate{
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML},
|
Offered: []string{MIMEYAML, MIMEXML, MIMEJSON, MIMETOML, MIMEYAML2},
|
||||||
Data: H{"foo": "bar"},
|
Data: H{"foo": "bar"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1228,7 +1284,7 @@ func TestContextNegotiationWithTOML(t *testing.T) {
|
|||||||
c.Request, _ = http.NewRequest("POST", "", nil)
|
c.Request, _ = http.NewRequest("POST", "", nil)
|
||||||
|
|
||||||
c.Negotiate(http.StatusOK, Negotiate{
|
c.Negotiate(http.StatusOK, Negotiate{
|
||||||
Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML},
|
Offered: []string{MIMETOML, MIMEXML, MIMEJSON, MIMEYAML, MIMEYAML2},
|
||||||
Data: H{"foo": "bar"},
|
Data: H{"foo": "bar"},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1657,6 +1713,30 @@ func TestContextBindWithXML(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextBindPlain(t *testing.T) {
|
||||||
|
// string
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEPlain)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
assert.NoError(t, c.BindPlain(&s))
|
||||||
|
assert.Equal(t, "test string", s)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
|
||||||
|
// []byte
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEPlain)
|
||||||
|
|
||||||
|
var bs []byte
|
||||||
|
|
||||||
|
assert.NoError(t, c.BindPlain(&bs))
|
||||||
|
assert.Equal(t, []byte("test []byte"), bs)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextBindHeader(t *testing.T) {
|
func TestContextBindHeader(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
@ -1803,6 +1883,30 @@ func TestContextShouldBindWithXML(t *testing.T) {
|
|||||||
assert.Equal(t, 0, w.Body.Len())
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindPlain(t *testing.T) {
|
||||||
|
// string
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test string`))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEPlain)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
assert.NoError(t, c.ShouldBindPlain(&s))
|
||||||
|
assert.Equal(t, "test string", s)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
// []byte
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(`test []byte`))
|
||||||
|
c.Request.Header.Add("Content-Type", MIMEPlain)
|
||||||
|
|
||||||
|
var bs []byte
|
||||||
|
|
||||||
|
assert.NoError(t, c.ShouldBindPlain(&bs))
|
||||||
|
assert.Equal(t, []byte("test []byte"), bs)
|
||||||
|
assert.Equal(t, 0, w.Body.Len())
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextShouldBindHeader(t *testing.T) {
|
func TestContextShouldBindHeader(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
c, _ := CreateTestContext(w)
|
c, _ := CreateTestContext(w)
|
||||||
@ -2234,6 +2338,81 @@ func TestContextShouldBindBodyWithTOML(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextShouldBindBodyWithPlain(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
bindingBody binding.BindingBody
|
||||||
|
body string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: " JSON & JSON-BODY ",
|
||||||
|
bindingBody: binding.JSON,
|
||||||
|
body: `{"foo":"FOO"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: " JSON & XML-BODY ",
|
||||||
|
bindingBody: binding.XML,
|
||||||
|
body: `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root>
|
||||||
|
<foo>FOO</foo>
|
||||||
|
</root>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: " JSON & YAML-BODY ",
|
||||||
|
bindingBody: binding.YAML,
|
||||||
|
body: `foo: FOO`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: " JSON & TOM-BODY ",
|
||||||
|
bindingBody: binding.TOML,
|
||||||
|
body: `foo=FOO`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: " JSON & Plain-BODY ",
|
||||||
|
bindingBody: binding.Plain,
|
||||||
|
body: `foo=FOO`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Logf("testing: %s", tt.name)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString(tt.body))
|
||||||
|
|
||||||
|
type typeJSON struct {
|
||||||
|
Foo string `json:"foo" binding:"required"`
|
||||||
|
}
|
||||||
|
objJSON := typeJSON{}
|
||||||
|
|
||||||
|
if tt.bindingBody == binding.Plain {
|
||||||
|
body := ""
|
||||||
|
assert.NoError(t, c.ShouldBindBodyWithPlain(&body))
|
||||||
|
assert.Equal(t, body, "foo=FOO")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.bindingBody == binding.JSON {
|
||||||
|
assert.NoError(t, c.ShouldBindBodyWithJSON(&objJSON))
|
||||||
|
assert.Equal(t, typeJSON{"FOO"}, objJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.bindingBody == binding.XML {
|
||||||
|
assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON))
|
||||||
|
assert.Equal(t, typeJSON{}, objJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.bindingBody == binding.YAML {
|
||||||
|
assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON))
|
||||||
|
assert.Equal(t, typeJSON{}, objJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.bindingBody == binding.TOML {
|
||||||
|
assert.Error(t, c.ShouldBindBodyWithJSON(&objJSON))
|
||||||
|
assert.Equal(t, typeJSON{}, objJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextGolangContext(t *testing.T) {
|
func TestContextGolangContext(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
|
||||||
|
7
debug.go
7
debug.go
@ -10,14 +10,15 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ginSupportMinGoVer = 18
|
const ginSupportMinGoVer = 21
|
||||||
|
|
||||||
// IsDebugging returns true if the framework is running in debug mode.
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
func IsDebugging() bool {
|
func IsDebugging() bool {
|
||||||
return ginMode == debugCode
|
return atomic.LoadInt32(&ginMode) == debugCode
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugPrintRouteFunc indicates debug log output format.
|
// DebugPrintRouteFunc indicates debug log output format.
|
||||||
@ -77,7 +78,7 @@ func getMinVer(v string) (uint64, error) {
|
|||||||
|
|
||||||
func debugPrintWARNINGDefault() {
|
func debugPrintWARNINGDefault() {
|
||||||
if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
|
if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
|
||||||
debugPrint(`[WARNING] Now Gin requires Go 1.18+.
|
debugPrint(`[WARNING] Now Gin requires Go 1.21+.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
|
|||||||
})
|
})
|
||||||
m, e := getMinVer(runtime.Version())
|
m, e := getMinVer(runtime.Version())
|
||||||
if e == nil && m < ginSupportMinGoVer {
|
if e == nil && m < ginSupportMinGoVer {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.18+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.21+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
|
||||||
}
|
}
|
||||||
|
41
docs/doc.md
41
docs/doc.md
@ -27,6 +27,7 @@
|
|||||||
- [Only Bind Query String](#only-bind-query-string)
|
- [Only Bind Query String](#only-bind-query-string)
|
||||||
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
||||||
- [Bind Uri](#bind-uri)
|
- [Bind Uri](#bind-uri)
|
||||||
|
- [Bind custom unmarshaler](#bind-custom-unmarshaler)
|
||||||
- [Bind Header](#bind-header)
|
- [Bind Header](#bind-header)
|
||||||
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
||||||
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
||||||
@ -899,6 +900,46 @@ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
|
|||||||
curl -v localhost:8088/thinkerou/not-uuid
|
curl -v localhost:8088/thinkerou/not-uuid
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bind custom unmarshaler
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Birthday string
|
||||||
|
|
||||||
|
func (b *Birthday) UnmarshalParam(param string) error {
|
||||||
|
*b = Birthday(strings.Replace(param, "-", "/", -1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
route := gin.Default()
|
||||||
|
var request struct {
|
||||||
|
Birthday Birthday `form:"birthday"`
|
||||||
|
}
|
||||||
|
route.GET("/test", func(ctx *gin.Context) {
|
||||||
|
_ = ctx.BindQuery(&request)
|
||||||
|
ctx.JSON(200, request.Birthday)
|
||||||
|
})
|
||||||
|
route.Run(":8088")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Test it with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl 'localhost:8088/test?birthday=2000-01-01'
|
||||||
|
```
|
||||||
|
Result
|
||||||
|
```sh
|
||||||
|
"2000/01/01"
|
||||||
|
```
|
||||||
|
|
||||||
### Bind Header
|
### Bind Header
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
52
fs.go
52
fs.go
@ -9,37 +9,43 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type onlyFilesFS struct {
|
// OnlyFilesFS implements an http.FileSystem without `Readdir` functionality.
|
||||||
fs http.FileSystem
|
type OnlyFilesFS struct {
|
||||||
|
FileSystem http.FileSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
type neuteredReaddirFile struct {
|
// Open passes `Open` to the upstream implementation without `Readdir` functionality.
|
||||||
http.File
|
func (o OnlyFilesFS) Open(name string) (http.File, error) {
|
||||||
}
|
f, err := o.FileSystem.Open(name)
|
||||||
|
|
||||||
// Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally
|
|
||||||
// in router.Static().
|
|
||||||
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
|
|
||||||
// a filesystem that prevents http.FileServer() to list the directory files.
|
|
||||||
func Dir(root string, listDirectory bool) http.FileSystem {
|
|
||||||
fs := http.Dir(root)
|
|
||||||
if listDirectory {
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
return &onlyFilesFS{fs}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open conforms to http.Filesystem.
|
|
||||||
func (fs onlyFilesFS) Open(name string) (http.File, error) {
|
|
||||||
f, err := fs.fs.Open(name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return neuteredReaddirFile{f}, nil
|
|
||||||
|
return neutralizedReaddirFile{f}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Readdir overrides the http.File default implementation.
|
// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.
|
||||||
func (f neuteredReaddirFile) Readdir(_ int) ([]os.FileInfo, error) {
|
type neutralizedReaddirFile struct {
|
||||||
|
http.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readdir overrides the http.File default implementation and always returns nil.
|
||||||
|
func (n neutralizedReaddirFile) Readdir(_ int) ([]os.FileInfo, error) {
|
||||||
// this disables directory listing
|
// this disables directory listing
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dir returns an http.FileSystem that can be used by http.FileServer().
|
||||||
|
// It is used internally in router.Static().
|
||||||
|
// if listDirectory == true, then it works the same as http.Dir(),
|
||||||
|
// otherwise it returns a filesystem that prevents http.FileServer() to list the directory files.
|
||||||
|
func Dir(root string, listDirectory bool) http.FileSystem {
|
||||||
|
fs := http.Dir(root)
|
||||||
|
|
||||||
|
if listDirectory {
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OnlyFilesFS{FileSystem: fs}
|
||||||
|
}
|
||||||
|
71
fs_test.go
Normal file
71
fs_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockFileSystem struct {
|
||||||
|
open func(name string) (http.File, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFileSystem) Open(name string) (http.File, error) {
|
||||||
|
return m.open(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnlyFilesFS_Open(t *testing.T) {
|
||||||
|
var testFile *os.File
|
||||||
|
mockFS := &mockFileSystem{
|
||||||
|
open: func(name string) (http.File, error) {
|
||||||
|
return testFile, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fs := &OnlyFilesFS{FileSystem: mockFS}
|
||||||
|
|
||||||
|
file, err := fs.Open("foo")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, testFile, file.(neutralizedReaddirFile).File)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnlyFilesFS_Open_err(t *testing.T) {
|
||||||
|
testError := errors.New("mock")
|
||||||
|
mockFS := &mockFileSystem{
|
||||||
|
open: func(_ string) (http.File, error) {
|
||||||
|
return nil, testError
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fs := &OnlyFilesFS{FileSystem: mockFS}
|
||||||
|
|
||||||
|
file, err := fs.Open("foo")
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, testError)
|
||||||
|
assert.Nil(t, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_neuteredReaddirFile_Readdir(t *testing.T) {
|
||||||
|
n := neutralizedReaddirFile{}
|
||||||
|
|
||||||
|
res, err := n.Readdir(0)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDir_listDirectory(t *testing.T) {
|
||||||
|
testRoot := "foo"
|
||||||
|
fs := Dir(testRoot, true)
|
||||||
|
|
||||||
|
assert.Equal(t, http.Dir(testRoot), fs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDir(t *testing.T) {
|
||||||
|
testRoot := "foo"
|
||||||
|
fs := Dir(testRoot, false)
|
||||||
|
|
||||||
|
assert.Equal(t, &OnlyFilesFS{FileSystem: http.Dir(testRoot)}, fs)
|
||||||
|
}
|
56
gin.go
56
gin.go
@ -17,6 +17,8 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
"github.com/gin-gonic/gin/render"
|
"github.com/gin-gonic/gin/render"
|
||||||
|
|
||||||
|
"github.com/quic-go/quic-go/http3"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
"golang.org/x/net/http2/h2c"
|
"golang.org/x/net/http2/h2c"
|
||||||
)
|
)
|
||||||
@ -316,7 +318,7 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
|||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
// With returns a new Engine instance with the provided options.
|
// With returns a Engine with the configuration set in the OptionFunc.
|
||||||
func (engine *Engine) With(opts ...OptionFunc) *Engine {
|
func (engine *Engine) With(opts ...OptionFunc) *Engine {
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(engine)
|
opt(engine)
|
||||||
@ -383,23 +385,6 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
|||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
|
|
||||||
// It is a shortcut for http.ListenAndServe(addr, router)
|
|
||||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
|
||||||
func (engine *Engine) Run(addr ...string) (err error) {
|
|
||||||
defer func() { debugPrintError(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)
|
|
||||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
|
||||||
err = http.ListenAndServe(address, engine.Handler())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
||||||
if engine.trustedProxies == nil {
|
if engine.trustedProxies == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -503,6 +488,23 @@ func parseIP(ip string) net.IP {
|
|||||||
return parsedIP
|
return parsedIP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
|
||||||
|
// It is a shortcut for http.ListenAndServe(addr, router)
|
||||||
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
|
func (engine *Engine) Run(addr ...string) (err error) {
|
||||||
|
defer func() { debugPrintError(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://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
address := resolveAddress(addr)
|
||||||
|
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||||
|
err = http.ListenAndServe(address, engine.Handler())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
|
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
|
||||||
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
@ -512,7 +514,7 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
|
|||||||
|
|
||||||
if engine.isUnsafeTrustedProxies() {
|
if engine.isUnsafeTrustedProxies() {
|
||||||
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
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.")
|
"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
|
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
|
||||||
@ -564,6 +566,22 @@ func (engine *Engine) RunFd(fd int) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunQUIC attaches the router to a http.Server and starts listening and serving QUIC requests.
|
||||||
|
// It is a shortcut for http3.ListenAndServeQUIC(addr, certFile, keyFile, router)
|
||||||
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
|
func (engine *Engine) RunQUIC(addr, certFile, keyFile string) (err error) {
|
||||||
|
debugPrint("Listening and serving QUIC on %s\n", addr)
|
||||||
|
defer func() { debugPrintError(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 = http3.ListenAndServeQUIC(addr, certFile, keyFile, engine.Handler())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests
|
// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||||
// through the specified net.Listener
|
// through the specified net.Listener
|
||||||
func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
||||||
|
@ -274,6 +274,22 @@ func TestBadUnixSocket(t *testing.T) {
|
|||||||
assert.Error(t, router.RunUnix("#/tmp/unix_unit_test"))
|
assert.Error(t, router.RunUnix("#/tmp/unix_unit_test"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunQUIC(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
go func() {
|
||||||
|
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||||
|
|
||||||
|
assert.NoError(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// have to wait for the goroutine to start and run the server
|
||||||
|
// otherwise the main thread will complete
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
|
||||||
|
assert.Error(t, router.RunQUIC(":8443", "./testdata/certificate/cert.pem", "./testdata/certificate/key.pem"))
|
||||||
|
testRequest(t, "https://localhost:8443/example")
|
||||||
|
}
|
||||||
|
|
||||||
func TestFileDescriptor(t *testing.T) {
|
func TestFileDescriptor(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
|
|
||||||
|
24
gin_test.go
24
gin_test.go
@ -14,6 +14,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -730,3 +731,26 @@ func TestWithOptionFunc(t *testing.T) {
|
|||||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"})
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test1", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest1"})
|
||||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"})
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: "GET", Handler: "github.com/gin-gonic/gin.handlerTest2"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Birthday string
|
||||||
|
|
||||||
|
func (b *Birthday) UnmarshalParam(param string) error {
|
||||||
|
*b = Birthday(strings.Replace(param, "-", "/", -1))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomUnmarshalStruct(t *testing.T) {
|
||||||
|
route := Default()
|
||||||
|
var request struct {
|
||||||
|
Birthday Birthday `form:"birthday"`
|
||||||
|
}
|
||||||
|
route.GET("/test", func(ctx *Context) {
|
||||||
|
_ = ctx.BindQuery(&request)
|
||||||
|
ctx.JSON(200, request.Birthday)
|
||||||
|
})
|
||||||
|
req := httptest.NewRequest("GET", "/test?birthday=2000-01-01", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
route.ServeHTTP(w, req)
|
||||||
|
assert.Equal(t, 200, w.Code)
|
||||||
|
assert.Equal(t, `"2000/01/01"`, w.Body.String())
|
||||||
|
}
|
||||||
|
41
go.mod
41
go.mod
@ -1,37 +1,48 @@
|
|||||||
module github.com/gin-gonic/gin
|
module github.com/gin-gonic/gin
|
||||||
|
|
||||||
go 1.20
|
go 1.21.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.11.0
|
github.com/bytedance/sonic v1.11.6
|
||||||
github.com/gin-contrib/sse v0.1.0
|
github.com/gin-contrib/sse v0.1.0
|
||||||
github.com/go-playground/validator/v10 v10.18.0
|
github.com/go-playground/validator/v10 v10.20.0
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1
|
github.com/pelletier/go-toml/v2 v2.2.2
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/quic-go/quic-go v0.43.1
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/ugorji/go/codec v1.2.12
|
github.com/ugorji/go/codec v1.2.12
|
||||||
golang.org/x/net v0.21.0
|
golang.org/x/net v0.25.0
|
||||||
google.golang.org/protobuf v1.33.0
|
google.golang.org/protobuf v1.34.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||||
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
golang.org/x/arch v0.7.0 // indirect
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
golang.org/x/crypto v0.19.0 // indirect
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||||
golang.org/x/sys v0.17.0 // indirect
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
|
||||||
|
golang.org/x/mod v0.11.0 // indirect
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.15.0 // indirect
|
||||||
|
golang.org/x/tools v0.9.1 // indirect
|
||||||
)
|
)
|
||||||
|
109
go.sum
109
go.sum
@ -1,14 +1,15 @@
|
|||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
github.com/bytedance/sonic v1.11.0 h1:FwNNv6Vu4z2Onf1++LNzxB/QhitD8wuTdpZzMTGITWo=
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
github.com/bytedance/sonic v1.11.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -16,68 +17,104 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq
|
|||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
|
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
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/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
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.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
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/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||||
|
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||||
|
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
|
github.com/quic-go/quic-go v0.43.1 h1:fLiMNfQVe9q2JvSsiXo4fXOEguXHGGl9+6gLp4RPeZQ=
|
||||||
|
github.com/quic-go/quic-go v0.43.1/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||||
|
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||||
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
// Use of this source code is governed by a MIT style
|
// Use of this source code is governed by a MIT style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
//go:build go1.20
|
|
||||||
|
|
||||||
package bytesconv
|
package bytesconv
|
||||||
|
|
||||||
import (
|
import (
|
@ -1,26 +0,0 @@
|
|||||||
// Copyright 2020 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.20
|
|
||||||
|
|
||||||
package bytesconv
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StringToBytes converts string to byte slice without a memory allocation.
|
|
||||||
func StringToBytes(s string) []byte {
|
|
||||||
return *(*[]byte)(unsafe.Pointer(
|
|
||||||
&struct {
|
|
||||||
string
|
|
||||||
Cap int
|
|
||||||
}{s, len(s)},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// BytesToString converts byte slice to string without a memory allocation.
|
|
||||||
func BytesToString(b []byte) string {
|
|
||||||
return *(*string)(unsafe.Pointer(&b))
|
|
||||||
}
|
|
20
mode.go
20
mode.go
@ -8,6 +8,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
)
|
)
|
||||||
@ -43,10 +44,8 @@ var DefaultWriter io.Writer = os.Stdout
|
|||||||
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
|
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
|
||||||
var DefaultErrorWriter io.Writer = os.Stderr
|
var DefaultErrorWriter io.Writer = os.Stderr
|
||||||
|
|
||||||
var (
|
var ginMode int32 = debugCode
|
||||||
ginMode = debugCode
|
var modeName atomic.Value
|
||||||
modeName = DebugMode
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
mode := os.Getenv(EnvGinMode)
|
mode := os.Getenv(EnvGinMode)
|
||||||
@ -64,17 +63,16 @@ func SetMode(value string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch value {
|
switch value {
|
||||||
case DebugMode:
|
case DebugMode, "":
|
||||||
ginMode = debugCode
|
atomic.StoreInt32(&ginMode, debugCode)
|
||||||
case ReleaseMode:
|
case ReleaseMode:
|
||||||
ginMode = releaseCode
|
atomic.StoreInt32(&ginMode, releaseCode)
|
||||||
case TestMode:
|
case TestMode:
|
||||||
ginMode = testCode
|
atomic.StoreInt32(&ginMode, testCode)
|
||||||
default:
|
default:
|
||||||
panic("gin mode unknown: " + value + " (available mode: debug release test)")
|
panic("gin mode unknown: " + value + " (available mode: debug release test)")
|
||||||
}
|
}
|
||||||
|
modeName.Store(value)
|
||||||
modeName = value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableBindValidation closes the default validator.
|
// DisableBindValidation closes the default validator.
|
||||||
@ -96,5 +94,5 @@ func EnableJsonDecoderDisallowUnknownFields() {
|
|||||||
|
|
||||||
// Mode returns current gin mode.
|
// Mode returns current gin mode.
|
||||||
func Mode() string {
|
func Mode() string {
|
||||||
return modeName
|
return modeName.Load().(string)
|
||||||
}
|
}
|
||||||
|
19
mode_test.go
19
mode_test.go
@ -5,8 +5,8 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"os"
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/binding"
|
"github.com/gin-gonic/gin/binding"
|
||||||
@ -18,31 +18,24 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetMode(t *testing.T) {
|
func TestSetMode(t *testing.T) {
|
||||||
assert.Equal(t, testCode, ginMode)
|
assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
|
||||||
assert.Equal(t, TestMode, Mode())
|
assert.Equal(t, TestMode, Mode())
|
||||||
os.Unsetenv(EnvGinMode)
|
os.Unsetenv(EnvGinMode)
|
||||||
|
|
||||||
SetMode("")
|
SetMode("")
|
||||||
assert.Equal(t, testCode, ginMode)
|
assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
|
||||||
assert.Equal(t, TestMode, Mode())
|
assert.Equal(t, TestMode, Mode())
|
||||||
|
|
||||||
tmp := flag.CommandLine
|
|
||||||
flag.CommandLine = flag.NewFlagSet("", flag.ContinueOnError)
|
|
||||||
SetMode("")
|
|
||||||
assert.Equal(t, debugCode, ginMode)
|
|
||||||
assert.Equal(t, DebugMode, Mode())
|
|
||||||
flag.CommandLine = tmp
|
|
||||||
|
|
||||||
SetMode(DebugMode)
|
SetMode(DebugMode)
|
||||||
assert.Equal(t, debugCode, ginMode)
|
assert.Equal(t, int32(debugCode), atomic.LoadInt32(&ginMode))
|
||||||
assert.Equal(t, DebugMode, Mode())
|
assert.Equal(t, DebugMode, Mode())
|
||||||
|
|
||||||
SetMode(ReleaseMode)
|
SetMode(ReleaseMode)
|
||||||
assert.Equal(t, releaseCode, ginMode)
|
assert.Equal(t, int32(releaseCode), atomic.LoadInt32(&ginMode))
|
||||||
assert.Equal(t, ReleaseMode, Mode())
|
assert.Equal(t, ReleaseMode, Mode())
|
||||||
|
|
||||||
SetMode(TestMode)
|
SetMode(TestMode)
|
||||||
assert.Equal(t, testCode, ginMode)
|
assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
|
||||||
assert.Equal(t, TestMode, Mode())
|
assert.Equal(t, TestMode, Mode())
|
||||||
|
|
||||||
assert.Panics(t, func() { SetMode("unknown") })
|
assert.Panics(t, func() { SetMode("unknown") })
|
||||||
|
@ -218,7 +218,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
|||||||
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
|
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
|
||||||
|
|
||||||
return func(c *Context) {
|
return func(c *Context) {
|
||||||
if _, noListing := fs.(*onlyFilesFS); noListing {
|
if _, noListing := fs.(*OnlyFilesFS); noListing {
|
||||||
c.Writer.WriteHeader(http.StatusNotFound)
|
c.Writer.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
tree.go
15
tree.go
@ -65,17 +65,10 @@ func (trees methodTrees) get(method string) *node {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a <= b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func longestCommonPrefix(a, b string) int {
|
func longestCommonPrefix(a, b string) int {
|
||||||
i := 0
|
i := 0
|
||||||
max := min(len(a), len(b))
|
max_ := min(len(a), len(b))
|
||||||
for i < max && a[i] == b[i] {
|
for i < max_ && a[i] == b[i] {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
return i
|
return i
|
||||||
@ -205,7 +198,7 @@ walk:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if a child with the next path byte exists
|
// Check if a child with the next path byte exists
|
||||||
for i, max := 0, len(n.indices); i < max; i++ {
|
for i, max_ := 0, len(n.indices); i < max_; i++ {
|
||||||
if c == n.indices[i] {
|
if c == n.indices[i] {
|
||||||
parentFullPathIndex += len(n.path)
|
parentFullPathIndex += len(n.path)
|
||||||
i = n.incrementChildPrio(i)
|
i = n.incrementChildPrio(i)
|
||||||
@ -770,7 +763,7 @@ walk: // Outer loop for walking the tree
|
|||||||
// Runes are up to 4 byte long,
|
// Runes are up to 4 byte long,
|
||||||
// -4 would definitely be another rune.
|
// -4 would definitely be another rune.
|
||||||
var off int
|
var off int
|
||||||
for max := min(npLen, 3); off < max; off++ {
|
for max_ := min(npLen, 3); off < max_; off++ {
|
||||||
if i := npLen - off; utf8.RuneStart(oldPath[i]) {
|
if i := npLen - off; utf8.RuneStart(oldPath[i]) {
|
||||||
// read rune from cached path
|
// read rune from cached path
|
||||||
rv, _ = utf8.DecodeRuneInString(oldPath[i:])
|
rv, _ = utf8.DecodeRuneInString(oldPath[i:])
|
||||||
|
@ -5,4 +5,4 @@
|
|||||||
package gin
|
package gin
|
||||||
|
|
||||||
// Version is the current gin framework's version.
|
// Version is the current gin framework's version.
|
||||||
const Version = "v1.9.1"
|
const Version = "v1.10.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user