mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-16 13:22:09 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
c4c50b0e02
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@ -46,4 +46,4 @@ jobs:
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
10
.github/workflows/gin.yml
vendored
10
.github/workflows/gin.yml
vendored
@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.16'
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3.1.0
|
||||
uses: golangci/golangci-lint-action@v3.2.0
|
||||
with:
|
||||
version: v1.45.0
|
||||
args: --verbose
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go }}
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
@ -52,7 +52,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{ matrix.go-build }}
|
||||
@ -65,7 +65,7 @@ jobs:
|
||||
run: make test
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
|
||||
notification-gitter:
|
||||
|
@ -86,7 +86,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
|
||||
To install Gin package, you need to install Go and set your Go workspace first.
|
||||
|
||||
1. The first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin.
|
||||
1. You first need [Go](https://golang.org/) installed (**version 1.14+ is required**), then you can use the below Go command to install Gin.
|
||||
|
||||
```sh
|
||||
$ go get -u github.com/gin-gonic/gin
|
||||
@ -658,7 +658,7 @@ func main() {
|
||||
|
||||
### Model binding and validation
|
||||
|
||||
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz).
|
||||
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
|
||||
|
||||
Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
|
||||
|
||||
@ -666,10 +666,10 @@ Note that you need to set the corresponding binding tag on all fields you want t
|
||||
|
||||
Also, Gin provides two sets of methods for binding:
|
||||
- **Type** - Must bind
|
||||
- **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`
|
||||
- **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML`
|
||||
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
|
||||
- **Type** - Should bind
|
||||
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`
|
||||
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`,
|
||||
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
|
||||
|
||||
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
|
||||
|
2
auth.go
2
auth.go
@ -31,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
for _, pair := range a {
|
||||
if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 {
|
||||
if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
|
||||
return pair.user, true
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ const (
|
||||
MIMEMSGPACK = "application/x-msgpack"
|
||||
MIMEMSGPACK2 = "application/msgpack"
|
||||
MIMEYAML = "application/x-yaml"
|
||||
MIMETOML = "application/toml"
|
||||
)
|
||||
|
||||
// Binding describes the interface which needs to be implemented for binding the
|
||||
@ -83,6 +84,7 @@ var (
|
||||
YAML = yamlBinding{}
|
||||
Uri = uriBinding{}
|
||||
Header = headerBinding{}
|
||||
TOML = tomlBinding{}
|
||||
)
|
||||
|
||||
// Default returns the appropriate Binding instance based on the HTTP method
|
||||
@ -103,6 +105,8 @@ func Default(method, contentType string) Binding {
|
||||
return MsgPack
|
||||
case MIMEYAML:
|
||||
return YAML
|
||||
case MIMETOML:
|
||||
return TOML
|
||||
case MIMEMultipartPOSTForm:
|
||||
return FormMultipart
|
||||
default: // case MIMEPOSTForm:
|
||||
|
@ -20,6 +20,7 @@ const (
|
||||
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||
MIMEPROTOBUF = "application/x-protobuf"
|
||||
MIMEYAML = "application/x-yaml"
|
||||
MIMETOML = "application/toml"
|
||||
)
|
||||
|
||||
// Binding describes the interface which needs to be implemented for binding the
|
||||
@ -79,6 +80,7 @@ var (
|
||||
YAML = yamlBinding{}
|
||||
Uri = uriBinding{}
|
||||
Header = headerBinding{}
|
||||
TOML = tomlBinding{}
|
||||
)
|
||||
|
||||
// Default returns the appropriate Binding instance based on the HTTP method
|
||||
@ -99,6 +101,8 @@ func Default(method, contentType string) Binding {
|
||||
return YAML
|
||||
case MIMEMultipartPOSTForm:
|
||||
return FormMultipart
|
||||
case MIMETOML:
|
||||
return TOML
|
||||
default: // case MIMEPOSTForm:
|
||||
return Form
|
||||
}
|
||||
|
@ -165,6 +165,9 @@ func TestBindingDefault(t *testing.T) {
|
||||
|
||||
assert.Equal(t, YAML, Default("POST", MIMEYAML))
|
||||
assert.Equal(t, YAML, Default("PUT", MIMEYAML))
|
||||
|
||||
assert.Equal(t, TOML, Default("POST", MIMETOML))
|
||||
assert.Equal(t, TOML, Default("PUT", MIMETOML))
|
||||
}
|
||||
|
||||
func TestBindingJSONNilBody(t *testing.T) {
|
||||
@ -454,6 +457,20 @@ func TestBindingXMLFail(t *testing.T) {
|
||||
"<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>")
|
||||
}
|
||||
|
||||
func TestBindingTOML(t *testing.T) {
|
||||
testBodyBinding(t,
|
||||
TOML, "toml",
|
||||
"/", "/",
|
||||
`foo="bar"`, `bar="foo"`)
|
||||
}
|
||||
|
||||
func TestBindingTOMLFail(t *testing.T) {
|
||||
testBodyBindingFail(t,
|
||||
TOML, "toml",
|
||||
"/", "/",
|
||||
`foo=\n"bar"`, `bar="foo"`)
|
||||
}
|
||||
|
||||
func TestBindingYAML(t *testing.T) {
|
||||
testBodyBinding(t,
|
||||
YAML, "yaml",
|
||||
@ -1339,10 +1356,10 @@ func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body
|
||||
err := b.Bind(req, &obj)
|
||||
assert.Error(t, err)
|
||||
|
||||
invalid_obj := FooStruct{}
|
||||
invalidobj := FooStruct{}
|
||||
req.Body = ioutil.NopCloser(strings.NewReader(`{"msg":"hello"}`))
|
||||
req.Header.Add("Content-Type", MIMEPROTOBUF)
|
||||
err = b.Bind(req, &invalid_obj)
|
||||
err = b.Bind(req, &invalidobj)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, err.Error(), "obj is not ProtoMessage")
|
||||
|
||||
|
31
binding/toml.go
Normal file
31
binding/toml.go
Normal file
@ -0,0 +1,31 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
type tomlBinding struct{}
|
||||
|
||||
func (tomlBinding) Name() string {
|
||||
return "toml"
|
||||
}
|
||||
|
||||
func decodeToml(r io.Reader, obj any) error {
|
||||
decoder := toml.NewDecoder(r)
|
||||
if err := decoder.Decode(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return decoder.Decode(obj)
|
||||
}
|
||||
|
||||
func (tomlBinding) Bind(req *http.Request, obj any) error {
|
||||
return decodeToml(req.Body, obj)
|
||||
}
|
||||
|
||||
func (tomlBinding) BindBody(body []byte, obj any) error {
|
||||
return decodeToml(bytes.NewReader(body), obj)
|
||||
}
|
22
binding/toml_test.go
Normal file
22
binding/toml_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
// 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.
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTOMLBindingBindBody(t *testing.T) {
|
||||
var s struct {
|
||||
Foo string `toml:"foo"`
|
||||
}
|
||||
tomlBody := `foo="FOO"`
|
||||
err := tomlBinding{}.BindBody([]byte(tomlBody), &s)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "FOO", s.Foo)
|
||||
}
|
28
context.go
28
context.go
@ -34,11 +34,15 @@ const (
|
||||
MIMEPOSTForm = binding.MIMEPOSTForm
|
||||
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
||||
MIMEYAML = binding.MIMEYAML
|
||||
MIMETOML = binding.MIMETOML
|
||||
)
|
||||
|
||||
// BodyBytesKey indicates a default body bytes key.
|
||||
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
|
||||
|
||||
// ContextKey is the key that a Context returns itself for.
|
||||
const ContextKey = "_gin-gonic/gin/contextkey"
|
||||
|
||||
// abortIndex represents a typical value used in abort functions.
|
||||
const abortIndex int8 = math.MaxInt8 >> 1
|
||||
|
||||
@ -98,6 +102,7 @@ func (c *Context) reset() {
|
||||
c.Accepted = nil
|
||||
c.queryCache = nil
|
||||
c.formCache = nil
|
||||
c.sameSite = 0
|
||||
*c.params = (*c.params)[:0]
|
||||
*c.skippedNodes = (*c.skippedNodes)[:0]
|
||||
}
|
||||
@ -632,6 +637,11 @@ func (c *Context) BindYAML(obj any) error {
|
||||
return c.MustBindWith(obj, binding.YAML)
|
||||
}
|
||||
|
||||
// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
|
||||
func (c *Context) BindTOML(obj interface{}) error {
|
||||
return c.MustBindWith(obj, binding.TOML)
|
||||
}
|
||||
|
||||
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
|
||||
func (c *Context) BindHeader(obj any) error {
|
||||
return c.MustBindWith(obj, binding.Header)
|
||||
@ -690,6 +700,11 @@ func (c *Context) ShouldBindYAML(obj any) error {
|
||||
return c.ShouldBindWith(obj, binding.YAML)
|
||||
}
|
||||
|
||||
// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
|
||||
func (c *Context) ShouldBindTOML(obj interface{}) error {
|
||||
return c.ShouldBindWith(obj, binding.TOML)
|
||||
}
|
||||
|
||||
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
|
||||
func (c *Context) ShouldBindHeader(obj any) error {
|
||||
return c.ShouldBindWith(obj, binding.Header)
|
||||
@ -961,6 +976,11 @@ func (c *Context) YAML(code int, obj any) {
|
||||
c.Render(code, render.YAML{Data: obj})
|
||||
}
|
||||
|
||||
// TOML serializes the given struct as TOML into the response body.
|
||||
func (c *Context) TOML(code int, obj interface{}) {
|
||||
c.Render(code, render.TOML{Data: obj})
|
||||
}
|
||||
|
||||
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
|
||||
func (c *Context) ProtoBuf(code int, obj any) {
|
||||
c.Render(code, render.ProtoBuf{Data: obj})
|
||||
@ -1065,6 +1085,7 @@ type Negotiate struct {
|
||||
XMLData any
|
||||
YAMLData any
|
||||
Data any
|
||||
TOMLData any
|
||||
}
|
||||
|
||||
// Negotiate calls different Render according to acceptable Accept format.
|
||||
@ -1086,6 +1107,10 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
||||
data := chooseData(config.YAMLData, config.Data)
|
||||
c.YAML(code, data)
|
||||
|
||||
case binding.MIMETOML:
|
||||
data := chooseData(config.TOMLData, config.Data)
|
||||
c.TOML(code, data)
|
||||
|
||||
default:
|
||||
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
|
||||
}
|
||||
@ -1162,6 +1187,9 @@ func (c *Context) Value(key any) any {
|
||||
if key == 0 {
|
||||
return c.Request
|
||||
}
|
||||
if key == ContextKey {
|
||||
return c
|
||||
}
|
||||
if keyAsString, ok := key.(string); ok {
|
||||
if val, exists := c.Get(keyAsString); exists {
|
||||
return val
|
||||
|
50
context_go17_test.go
Normal file
50
context_go17_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
// +build go1.7
|
||||
|
||||
package gin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type interceptedWriter struct {
|
||||
ResponseWriter
|
||||
b *bytes.Buffer
|
||||
}
|
||||
|
||||
func (i interceptedWriter) WriteHeader(code int) {
|
||||
i.Header().Del("X-Test")
|
||||
i.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
func TestInterceptedHeader(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, r := CreateTestContext(w)
|
||||
|
||||
r.Use(func(c *Context) {
|
||||
i := interceptedWriter{
|
||||
ResponseWriter: c.Writer,
|
||||
b: bytes.NewBuffer(nil),
|
||||
}
|
||||
c.Writer = i
|
||||
c.Next()
|
||||
c.Header("X-Test", "overridden")
|
||||
c.Writer = i.ResponseWriter
|
||||
})
|
||||
r.GET("/", func(c *Context) {
|
||||
c.Header("X-Test", "original")
|
||||
c.Header("X-Test-2", "present")
|
||||
c.String(http.StatusOK, "hello world")
|
||||
})
|
||||
c.Request = httptest.NewRequest("GET", "/", nil)
|
||||
r.HandleContext(c)
|
||||
// Result() has headers frozen when WriteHeaderNow() has been called
|
||||
// Compared to this time, this is when the response headers will be flushed
|
||||
// As response is flushed on c.String, the Header cannot be set by the first
|
||||
// middleware. Assert this
|
||||
assert.Equal(t, "", w.Result().Header.Get("X-Test"))
|
||||
assert.Equal(t, "present", w.Result().Header.Get("X-Test-2"))
|
||||
}
|
@ -1773,6 +1773,23 @@ func TestContextShouldBindWithYAML(t *testing.T) {
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
|
||||
func TestContextShouldBindWithTOML(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
|
||||
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo='bar'\nbar= 'foo'"))
|
||||
c.Request.Header.Add("Content-Type", MIMETOML) // set fake content-type
|
||||
|
||||
var obj struct {
|
||||
Foo string `toml:"foo"`
|
||||
Bar string `toml:"bar"`
|
||||
}
|
||||
assert.NoError(t, c.ShouldBindTOML(&obj))
|
||||
assert.Equal(t, "foo", obj.Bar)
|
||||
assert.Equal(t, "bar", obj.Foo)
|
||||
assert.Equal(t, 0, w.Body.Len())
|
||||
}
|
||||
|
||||
func TestContextBadAutoShouldBind(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := CreateTestContext(w)
|
||||
@ -1880,6 +1897,7 @@ func TestContextGolangContext(t *testing.T) {
|
||||
assert.Equal(t, ti, time.Time{})
|
||||
assert.False(t, ok)
|
||||
assert.Equal(t, c.Value(0), c.Request)
|
||||
assert.Equal(t, c.Value(ContextKey), c)
|
||||
assert.Nil(t, c.Value("foo"))
|
||||
|
||||
c.Set("foo", "bar")
|
||||
|
29
gin.go
29
gin.go
@ -67,10 +67,10 @@ type RoutesInfo []RouteInfo
|
||||
|
||||
// Trusted platforms
|
||||
const (
|
||||
// When running on Google App Engine. Trust X-Appengine-Remote-Addr
|
||||
// PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr
|
||||
// for determining the client's IP
|
||||
PlatformGoogleAppEngine = "X-Appengine-Remote-Addr"
|
||||
// When using Cloudflare's CDN. Trust CF-Connecting-IP for determining
|
||||
// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
|
||||
// the client's IP
|
||||
PlatformCloudflare = "CF-Connecting-IP"
|
||||
)
|
||||
@ -80,14 +80,14 @@ const (
|
||||
type Engine struct {
|
||||
RouterGroup
|
||||
|
||||
// Enables automatic redirection if the current route can't be matched but a
|
||||
// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
|
||||
// handler for the path with (without) the trailing slash exists.
|
||||
// For example if /foo/ is requested but a route only exists for /foo, the
|
||||
// client is redirected to /foo with http status code 301 for GET requests
|
||||
// and 307 for all other request methods.
|
||||
RedirectTrailingSlash bool
|
||||
|
||||
// If enabled, the router tries to fix the current request path, if no
|
||||
// RedirectFixedPath if enabled, the router tries to fix the current request path, if no
|
||||
// handle is registered for it.
|
||||
// First superfluous path elements like ../ or // are removed.
|
||||
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
||||
@ -98,7 +98,7 @@ type Engine struct {
|
||||
// RedirectTrailingSlash is independent of this option.
|
||||
RedirectFixedPath bool
|
||||
|
||||
// If enabled, the router checks if another method is allowed for the
|
||||
// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
|
||||
// current route, if the current request can not be routed.
|
||||
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||
// and HTTP status code 405.
|
||||
@ -106,21 +106,22 @@ type Engine struct {
|
||||
// handler.
|
||||
HandleMethodNotAllowed bool
|
||||
|
||||
// If enabled, client IP will be parsed from the request's headers that
|
||||
// ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that
|
||||
// match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was
|
||||
// fetched, it falls back to the IP obtained from
|
||||
// `(*gin.Context).Request.RemoteAddr`.
|
||||
ForwardedByClientIP bool
|
||||
|
||||
// DEPRECATED: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
|
||||
// AppEngine was deprecated.
|
||||
// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
|
||||
// #726 #755 If enabled, it will trust some headers starting with
|
||||
// 'X-AppEngine...' for better integration with that PaaS.
|
||||
AppEngine bool
|
||||
|
||||
// If enabled, the url.RawPath will be used to find parameters.
|
||||
// UseRawPath if enabled, the url.RawPath will be used to find parameters.
|
||||
UseRawPath bool
|
||||
|
||||
// If true, the path value will be unescaped.
|
||||
// UnescapePathValues if true, the path value will be unescaped.
|
||||
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
|
||||
// as url.Path gonna be used, which is already unescaped.
|
||||
UnescapePathValues bool
|
||||
@ -129,21 +130,21 @@ type Engine struct {
|
||||
// See the PR #1817 and issue #1644
|
||||
RemoveExtraSlash bool
|
||||
|
||||
// List of headers used to obtain the client IP when
|
||||
// RemoteIPHeaders list of headers used to obtain the client IP when
|
||||
// `(*gin.Engine).ForwardedByClientIP` is `true` and
|
||||
// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
|
||||
// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
|
||||
RemoteIPHeaders []string
|
||||
|
||||
// If set to a constant of value gin.Platform*, trusts the headers set by
|
||||
// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by
|
||||
// that platform, for example to determine the client IP
|
||||
TrustedPlatform string
|
||||
|
||||
// Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
|
||||
// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
|
||||
// method call.
|
||||
MaxMultipartMemory int64
|
||||
|
||||
// Enable h2c support.
|
||||
// UseH2C enable h2c support.
|
||||
UseH2C bool
|
||||
|
||||
delims render.Delims
|
||||
@ -194,7 +195,7 @@ func New() *Engine {
|
||||
trees: make(methodTrees, 0, 9),
|
||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||
secureJSONPrefix: "while(1);",
|
||||
trustedProxies: []string{"0.0.0.0/0"},
|
||||
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
||||
trustedCIDRs: defaultTrustedCIDRs,
|
||||
}
|
||||
engine.RouterGroup.engine = engine
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@ -281,7 +282,16 @@ func TestFileDescriptor(t *testing.T) {
|
||||
listener, err := net.ListenTCP("tcp", addr)
|
||||
assert.NoError(t, err)
|
||||
socketFile, err := listener.File()
|
||||
if isWindows() {
|
||||
// not supported by windows, it is unimplemented now
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
if socketFile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||
@ -547,3 +557,7 @@ func TestTreeRunDynamicRouting(t *testing.T) {
|
||||
testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found")
|
||||
testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found")
|
||||
}
|
||||
|
||||
func isWindows() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ func TestLoadHTMLGlobFromFuncMap(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
|
||||
assert.Equal(t, "Date: 2017/07/01", string(resp))
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -320,7 +320,7 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
|
||||
}
|
||||
|
||||
resp, _ := ioutil.ReadAll(res.Body)
|
||||
assert.Equal(t, "Date: 2017/07/01\n", string(resp))
|
||||
assert.Equal(t, "Date: 2017/07/01", string(resp))
|
||||
}
|
||||
|
||||
func TestAddRoute(t *testing.T) {
|
||||
|
@ -296,8 +296,8 @@ func TestShouldBindUri(t *testing.T) {
|
||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||
var person Person
|
||||
assert.NoError(t, c.ShouldBindUri(&person))
|
||||
assert.True(t, "" != person.Name)
|
||||
assert.True(t, "" != person.ID)
|
||||
assert.True(t, person.Name != "")
|
||||
assert.True(t, person.ID != "")
|
||||
c.String(http.StatusOK, "ShouldBindUri test OK")
|
||||
})
|
||||
|
||||
@ -318,8 +318,8 @@ func TestBindUri(t *testing.T) {
|
||||
router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
|
||||
var person Person
|
||||
assert.NoError(t, c.BindUri(&person))
|
||||
assert.True(t, "" != person.Name)
|
||||
assert.True(t, "" != person.ID)
|
||||
assert.True(t, person.Name != "")
|
||||
assert.True(t, person.ID != "")
|
||||
c.String(http.StatusOK, "BindUri test OK")
|
||||
})
|
||||
|
||||
|
7
go.mod
7
go.mod
@ -5,13 +5,14 @@ go 1.18
|
||||
require (
|
||||
github.com/gin-contrib/sse v0.1.0
|
||||
github.com/go-playground/validator/v10 v10.10.0
|
||||
github.com/goccy/go-json v0.9.5
|
||||
github.com/goccy/go-json v0.9.7
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/mattn/go-isatty v0.0.14
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/pelletier/go-toml/v2 v2.0.0-beta.6
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/ugorji/go/codec v1.2.7
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
||||
google.golang.org/protobuf v1.27.1
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
|
15
go.sum
15
go.sum
@ -12,8 +12,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/goccy/go-json v0.9.5 h1:ooSMW526ZjK+EaL5elrSyN2EzIfi/3V0m4+HJEDYLik=
|
||||
github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@ -36,6 +36,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OH
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
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/pelletier/go-toml/v2 v2.0.0-beta.6 h1:JFNqj2afbbhCqTiyN16D7Tudc/aaDzE2FBDk+VlBQnE=
|
||||
github.com/pelletier/go-toml/v2 v2.0.0-beta.6/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@ -45,8 +47,11 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
@ -67,8 +72,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
@ -44,7 +44,7 @@ type LoggerConfig struct {
|
||||
// Optional. Default value is gin.DefaultWriter.
|
||||
Output io.Writer
|
||||
|
||||
// SkipPaths is a url path array which logs are not written.
|
||||
// SkipPaths is an url path array which logs are not written.
|
||||
// Optional.
|
||||
SkipPaths []string
|
||||
}
|
||||
@ -161,12 +161,12 @@ func ForceConsoleColor() {
|
||||
consoleColorMode = forceColor
|
||||
}
|
||||
|
||||
// ErrorLogger returns a handlerfunc for any error type.
|
||||
// ErrorLogger returns a HandlerFunc for any error type.
|
||||
func ErrorLogger() HandlerFunc {
|
||||
return ErrorLoggerT(ErrorTypeAny)
|
||||
}
|
||||
|
||||
// ErrorLoggerT returns a handlerfunc for a given error type.
|
||||
// ErrorLoggerT returns a HandlerFunc for a given error type.
|
||||
func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
||||
return func(c *Context) {
|
||||
c.Next()
|
||||
|
@ -208,6 +208,7 @@ func TestLoggerWithConfigFormatting(t *testing.T) {
|
||||
// set dummy ClientIP
|
||||
c.Request.Header.Set("X-Forwarded-For", "20.20.20.20")
|
||||
gotKeys = c.Keys
|
||||
time.Sleep(time.Millisecond)
|
||||
})
|
||||
PerformRequest(router, "GET", "/example?a=100")
|
||||
|
||||
|
5
mode.go
5
mode.go
@ -5,6 +5,7 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
@ -54,8 +55,12 @@ func init() {
|
||||
// SetMode sets gin mode according to input string.
|
||||
func SetMode(value string) {
|
||||
if value == "" {
|
||||
if flag.Lookup("test.v") != nil {
|
||||
value = TestMode
|
||||
} else {
|
||||
value = DebugMode
|
||||
}
|
||||
}
|
||||
|
||||
switch value {
|
||||
case DebugMode:
|
||||
|
@ -5,6 +5,7 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@ -21,9 +22,16 @@ func TestSetMode(t *testing.T) {
|
||||
assert.Equal(t, TestMode, Mode())
|
||||
os.Unsetenv(EnvGinMode)
|
||||
|
||||
SetMode("")
|
||||
assert.Equal(t, testCode, ginMode)
|
||||
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)
|
||||
assert.Equal(t, debugCode, ginMode)
|
||||
|
@ -30,6 +30,7 @@ var (
|
||||
_ Render = Reader{}
|
||||
_ Render = AsciiJSON{}
|
||||
_ Render = ProtoBuf{}
|
||||
_ Render = TOML{}
|
||||
)
|
||||
|
||||
func writeContentType(w http.ResponseWriter, value []string) {
|
||||
|
32
render/toml.go
Normal file
32
render/toml.go
Normal file
@ -0,0 +1,32 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
// TOML contains the given interface object.
|
||||
type TOML struct {
|
||||
Data any
|
||||
}
|
||||
|
||||
var TOMLContentType = []string{"application/toml; charset=utf-8"}
|
||||
|
||||
// Render (TOML) marshals the given interface object and writes data with custom ContentType.
|
||||
func (r TOML) Render(w http.ResponseWriter) error {
|
||||
r.WriteContentType(w)
|
||||
|
||||
bytes, err := toml.Marshal(r.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(bytes)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteContentType (TOML) writes TOML ContentType for response.
|
||||
func (r TOML) WriteContentType(w http.ResponseWriter) {
|
||||
writeContentType(w, TOMLContentType)
|
||||
}
|
@ -23,23 +23,23 @@ type ResponseWriter interface {
|
||||
http.Flusher
|
||||
http.CloseNotifier
|
||||
|
||||
// Returns the HTTP response status code of the current request.
|
||||
// Status returns the HTTP response status code of the current request.
|
||||
Status() int
|
||||
|
||||
// Returns the number of bytes already written into the response http body.
|
||||
// Size returns the number of bytes already written into the response http body.
|
||||
// See Written()
|
||||
Size() int
|
||||
|
||||
// Writes the string into the response body.
|
||||
// WriteString writes the string into the response body.
|
||||
WriteString(string) (int, error)
|
||||
|
||||
// Returns true if the response body was already written.
|
||||
// Written returns true if the response body was already written.
|
||||
Written() bool
|
||||
|
||||
// Forces to write the http header (status code + headers).
|
||||
// WriteHeaderNow forces to write the http header (status code + headers).
|
||||
WriteHeaderNow()
|
||||
|
||||
// get the http.Pusher for server push
|
||||
// Pusher get the http.Pusher for server push
|
||||
Pusher() http.Pusher
|
||||
}
|
||||
|
||||
@ -107,12 +107,12 @@ func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify implements the http.CloseNotify interface.
|
||||
// CloseNotify implements the http.CloseNotifier interface.
|
||||
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// Flush implements the http.Flush interface.
|
||||
// Flush implements the http.Flusher interface.
|
||||
func (w *responseWriter) Flush() {
|
||||
w.WriteHeaderNow()
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
|
Loading…
x
Reference in New Issue
Block a user