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:
|
||||
matrix:
|
||||
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:
|
||||
["", "-tags nomsgpack", '-tags "sonic avx"', "-tags go_json", "-race"]
|
||||
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"))
|
||||
}
|
||||
|
||||
func TestContextFormFileFailed(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(buf)
|
||||
mw.Close()
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest("POST", "/", nil)
|
||||
c.Request.Header.Set("Content-Type", mw.FormDataContentType())
|
||||
c.engine.MaxMultipartMemory = 8 << 20
|
||||
f, err := c.FormFile("file")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, f)
|
||||
}
|
||||
|
||||
func TestContextMultipartForm(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(buf)
|
||||
|
5
debug.go
5
debug.go
@ -10,6 +10,7 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const ginSupportMinGoVer = 18
|
||||
@ -17,7 +18,7 @@ const ginSupportMinGoVer = 18
|
||||
// IsDebugging returns true if the framework is running in debug mode.
|
||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||
func IsDebugging() bool {
|
||||
return ginMode == debugCode
|
||||
return atomic.LoadInt32(&ginMode) == debugCode
|
||||
}
|
||||
|
||||
// DebugPrintRouteFunc indicates debug log output format.
|
||||
@ -77,7 +78,7 @@ func getMinVer(v string) (uint64, error) {
|
||||
|
||||
func debugPrintWARNINGDefault() {
|
||||
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())
|
||||
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 {
|
||||
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"
|
||||
)
|
||||
|
||||
type onlyFilesFS struct {
|
||||
fs http.FileSystem
|
||||
// OnlyFilesFS implements an http.FileSystem without `Readdir` functionality.
|
||||
type OnlyFilesFS struct {
|
||||
FileSystem http.FileSystem
|
||||
}
|
||||
|
||||
type neuteredReaddirFile struct {
|
||||
http.File
|
||||
}
|
||||
// Open passes `Open` to the upstream implementation without `Readdir` functionality.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return neuteredReaddirFile{f}, nil
|
||||
|
||||
return neutralizedReaddirFile{f}, nil
|
||||
}
|
||||
|
||||
// Readdir overrides the http.File default implementation.
|
||||
func (f neuteredReaddirFile) Readdir(_ int) ([]os.FileInfo, error) {
|
||||
// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.
|
||||
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
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20
|
||||
|
||||
package bytesconv
|
||||
|
||||
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"
|
||||
"io"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
|
||||
"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
|
||||
var DefaultErrorWriter io.Writer = os.Stderr
|
||||
|
||||
var (
|
||||
ginMode = debugCode
|
||||
modeName = DebugMode
|
||||
)
|
||||
var ginMode int32 = debugCode
|
||||
var modeName atomic.Value
|
||||
|
||||
func init() {
|
||||
mode := os.Getenv(EnvGinMode)
|
||||
@ -64,17 +63,16 @@ func SetMode(value string) {
|
||||
}
|
||||
|
||||
switch value {
|
||||
case DebugMode:
|
||||
ginMode = debugCode
|
||||
case DebugMode, "":
|
||||
atomic.StoreInt32(&ginMode, debugCode)
|
||||
case ReleaseMode:
|
||||
ginMode = releaseCode
|
||||
atomic.StoreInt32(&ginMode, releaseCode)
|
||||
case TestMode:
|
||||
ginMode = testCode
|
||||
atomic.StoreInt32(&ginMode, testCode)
|
||||
default:
|
||||
panic("gin mode unknown: " + value + " (available mode: debug release test)")
|
||||
}
|
||||
|
||||
modeName = value
|
||||
modeName.Store(value)
|
||||
}
|
||||
|
||||
// DisableBindValidation closes the default validator.
|
||||
@ -96,5 +94,5 @@ func EnableJsonDecoderDisallowUnknownFields() {
|
||||
|
||||
// Mode returns current gin mode.
|
||||
func Mode() string {
|
||||
return modeName
|
||||
return modeName.Load().(string)
|
||||
}
|
||||
|
19
mode_test.go
19
mode_test.go
@ -5,8 +5,8 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
@ -18,31 +18,24 @@ func init() {
|
||||
}
|
||||
|
||||
func TestSetMode(t *testing.T) {
|
||||
assert.Equal(t, testCode, ginMode)
|
||||
assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
|
||||
assert.Equal(t, TestMode, Mode())
|
||||
os.Unsetenv(EnvGinMode)
|
||||
|
||||
SetMode("")
|
||||
assert.Equal(t, testCode, ginMode)
|
||||
assert.Equal(t, int32(testCode), atomic.LoadInt32(&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)
|
||||
assert.Equal(t, int32(debugCode), atomic.LoadInt32(&ginMode))
|
||||
assert.Equal(t, DebugMode, Mode())
|
||||
|
||||
SetMode(ReleaseMode)
|
||||
assert.Equal(t, releaseCode, ginMode)
|
||||
assert.Equal(t, int32(releaseCode), atomic.LoadInt32(&ginMode))
|
||||
assert.Equal(t, ReleaseMode, Mode())
|
||||
|
||||
SetMode(TestMode)
|
||||
assert.Equal(t, testCode, ginMode)
|
||||
assert.Equal(t, int32(testCode), atomic.LoadInt32(&ginMode))
|
||||
assert.Equal(t, TestMode, Mode())
|
||||
|
||||
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))
|
||||
|
||||
return func(c *Context) {
|
||||
if _, noListing := fs.(*onlyFilesFS); noListing {
|
||||
if _, noListing := fs.(*OnlyFilesFS); noListing {
|
||||
c.Writer.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user