mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-14 20:22:20 +08:00
Merge branch 'master' into http3
This commit is contained in:
commit
085f67940d
2
.github/workflows/gin.yml
vendored
2
.github/workflows/gin.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
|||||||
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.20", "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:
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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)
|
||||||
|
5
debug.go
5
debug.go
@ -10,6 +10,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ginSupportMinGoVer = 18
|
const ginSupportMinGoVer = 18
|
||||||
@ -17,7 +18,7 @@ const ginSupportMinGoVer = 18
|
|||||||
// 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.20+.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
@ -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.20+.\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)
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user