mirror of
https://github.com/gin-gonic/gin.git
synced 2026-07-07 02:41:14 +08:00
Compare commits
7 Commits
b2ddea48e0
...
213dfcb308
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
213dfcb308 | ||
|
|
5fad976b37 | ||
|
|
93ff771e6d | ||
|
|
96f63d68d3 | ||
|
|
5f94d6f3e8 | ||
|
|
51ef7a6a10 | ||
|
|
22b88e0ed1 |
@ -18,15 +18,8 @@ linters:
|
||||
- wastedassign
|
||||
settings:
|
||||
gosec:
|
||||
includes:
|
||||
- G102
|
||||
- G106
|
||||
- G108
|
||||
- G109
|
||||
- G111
|
||||
- G112
|
||||
- G201
|
||||
- G203
|
||||
excludes:
|
||||
- G115
|
||||
perfsprint:
|
||||
int-conversion: true
|
||||
err-error: true
|
||||
|
||||
10
context.go
10
context.go
@ -55,6 +55,14 @@ const ContextRequestKey ContextKeyType = 0
|
||||
// abortIndex represents a typical value used in abort functions.
|
||||
const abortIndex int8 = math.MaxInt8 >> 1
|
||||
|
||||
// safeInt8 converts int to int8 safely, capping at math.MaxInt8
|
||||
func safeInt8(n int) int8 {
|
||||
if n > math.MaxInt8 {
|
||||
return math.MaxInt8
|
||||
}
|
||||
return int8(n)
|
||||
}
|
||||
|
||||
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
||||
// manage the flow, validate the JSON of a request and render a JSON response for example.
|
||||
type Context struct {
|
||||
@ -186,7 +194,7 @@ func (c *Context) FullPath() string {
|
||||
// See example in GitHub.
|
||||
func (c *Context) Next() {
|
||||
c.index++
|
||||
for c.index < int8(len(c.handlers)) {
|
||||
for c.index < safeInt8(len(c.handlers)) {
|
||||
if c.handlers[c.index] != nil {
|
||||
c.handlers[c.index](c)
|
||||
}
|
||||
|
||||
68
gin.go
68
gin.go
@ -23,10 +23,12 @@ import (
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
const defaultMultipartMemory = 32 << 20 // 32 MB
|
||||
const escapedColon = "\\:"
|
||||
const colon = ":"
|
||||
const backslash = "\\"
|
||||
const (
|
||||
defaultMultipartMemory = 32 << 20 // 32 MB
|
||||
escapedColon = "\\:"
|
||||
colon = ":"
|
||||
backslash = "\\"
|
||||
)
|
||||
|
||||
var (
|
||||
default404Body = []byte("404 page not found")
|
||||
@ -46,8 +48,10 @@ var defaultTrustedCIDRs = []*net.IPNet{
|
||||
},
|
||||
}
|
||||
|
||||
var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
|
||||
var regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
|
||||
var (
|
||||
regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
|
||||
regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
|
||||
)
|
||||
|
||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||
type HandlerFunc func(*Context)
|
||||
@ -94,6 +98,10 @@ const (
|
||||
type Engine struct {
|
||||
RouterGroup
|
||||
|
||||
// routeTreesUpdated ensures that the initialization or update of the route trees
|
||||
// (used for routing HTTP requests) happens only once, even if called multiple times concurrently.
|
||||
routeTreesUpdated sync.Once
|
||||
|
||||
// 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
|
||||
@ -170,8 +178,10 @@ type Engine struct {
|
||||
FuncMap template.FuncMap
|
||||
allNoRoute HandlersChain
|
||||
allNoMethod HandlersChain
|
||||
allAutoRedirect HandlersChain
|
||||
noRoute HandlersChain
|
||||
noMethod HandlersChain
|
||||
autoRedirect HandlersChain
|
||||
pool sync.Pool
|
||||
trees methodTrees
|
||||
maxParams uint16
|
||||
@ -324,6 +334,13 @@ func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
||||
engine.rebuild405Handlers()
|
||||
}
|
||||
|
||||
// AutoRedirect sets the handlers called when auto redirected
|
||||
// (RedirectTrailingSlash and RedirectFixedPath)
|
||||
func (engine *Engine) AutoRedirect(handlers ...HandlerFunc) {
|
||||
engine.autoRedirect = handlers
|
||||
engine.rebuildAutoRedirectHandlers()
|
||||
}
|
||||
|
||||
// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
|
||||
// included in the handlers chain for every single request. Even 404, 405, static files...
|
||||
// For example, this is the right place for a logger or error management middleware.
|
||||
@ -331,6 +348,7 @@ func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||
engine.RouterGroup.Use(middleware...)
|
||||
engine.rebuild404Handlers()
|
||||
engine.rebuild405Handlers()
|
||||
engine.rebuildAutoRedirectHandlers()
|
||||
return engine
|
||||
}
|
||||
|
||||
@ -351,6 +369,10 @@ func (engine *Engine) rebuild405Handlers() {
|
||||
engine.allNoMethod = engine.combineHandlers(engine.noMethod)
|
||||
}
|
||||
|
||||
func (engine *Engine) rebuildAutoRedirectHandlers() {
|
||||
engine.allAutoRedirect = engine.combineHandlers(engine.autoRedirect)
|
||||
}
|
||||
|
||||
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||
assert1(path[0] == '/', "path must begin with '/'")
|
||||
assert1(method != "", "HTTP method can not be empty")
|
||||
@ -537,7 +559,11 @@ func (engine *Engine) Run(addr ...string) (err error) {
|
||||
engine.updateRouteTrees()
|
||||
address := resolveAddress(addr)
|
||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||
err = http.ListenAndServe(address, engine.Handler())
|
||||
server := &http.Server{ // #nosec G112
|
||||
Addr: address,
|
||||
Handler: engine.Handler(),
|
||||
}
|
||||
err = server.ListenAndServe()
|
||||
return
|
||||
}
|
||||
|
||||
@ -553,7 +579,11 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
|
||||
"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())
|
||||
server := &http.Server{ // #nosec G112
|
||||
Addr: addr,
|
||||
Handler: engine.Handler(),
|
||||
}
|
||||
err = server.ListenAndServeTLS(certFile, keyFile)
|
||||
return
|
||||
}
|
||||
|
||||
@ -576,7 +606,10 @@ func (engine *Engine) RunUnix(file string) (err error) {
|
||||
defer listener.Close()
|
||||
defer os.Remove(file)
|
||||
|
||||
err = http.Serve(listener, engine.Handler())
|
||||
server := &http.Server{ // #nosec G112
|
||||
Handler: engine.Handler(),
|
||||
}
|
||||
err = server.Serve(listener)
|
||||
return
|
||||
}
|
||||
|
||||
@ -630,12 +663,19 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
||||
"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||
}
|
||||
|
||||
err = http.Serve(listener, engine.Handler())
|
||||
server := &http.Server{ // #nosec G112
|
||||
Handler: engine.Handler(),
|
||||
}
|
||||
err = server.Serve(listener)
|
||||
return
|
||||
}
|
||||
|
||||
// ServeHTTP conforms to the http.Handler interface.
|
||||
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
engine.routeTreesUpdated.Do(func() {
|
||||
engine.updateRouteTrees()
|
||||
})
|
||||
|
||||
c := engine.pool.Get().(*Context)
|
||||
c.writermem.reset(w)
|
||||
c.Request = req
|
||||
@ -692,6 +732,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
return
|
||||
}
|
||||
if httpMethod != http.MethodConnect && rPath != "/" {
|
||||
c.handlers = engine.allAutoRedirect
|
||||
if value.tsr && engine.RedirectTrailingSlash {
|
||||
redirectTrailingSlash(c)
|
||||
return
|
||||
@ -776,13 +817,14 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||
|
||||
func redirectRequest(c *Context) {
|
||||
req := c.Request
|
||||
rPath := req.URL.Path
|
||||
rURL := req.URL.String()
|
||||
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
if req.Method != http.MethodGet {
|
||||
code = http.StatusTemporaryRedirect
|
||||
}
|
||||
c.Next()
|
||||
|
||||
rPath := req.URL.Path
|
||||
rURL := req.URL.String()
|
||||
debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
|
||||
http.Redirect(c.Writer, req, rURL, code)
|
||||
c.writermem.WriteHeaderNow()
|
||||
|
||||
152
gin_test.go
152
gin_test.go
@ -578,6 +578,59 @@ func TestNoMethodWithGlobalHandlers(t *testing.T) {
|
||||
compareFunc(t, router.allNoMethod[2], middleware0)
|
||||
}
|
||||
|
||||
func TestAutoRedirectWithoutGlobalHandlers(t *testing.T) {
|
||||
var middleware0 HandlerFunc = func(c *Context) {}
|
||||
var middleware1 HandlerFunc = func(c *Context) {}
|
||||
|
||||
router := New()
|
||||
|
||||
router.AutoRedirect(middleware0)
|
||||
assert.Nil(t, router.Handlers)
|
||||
assert.Len(t, router.autoRedirect, 1)
|
||||
assert.Len(t, router.allAutoRedirect, 1)
|
||||
compareFunc(t, router.autoRedirect[0], middleware0)
|
||||
compareFunc(t, router.allAutoRedirect[0], middleware0)
|
||||
|
||||
router.AutoRedirect(middleware1, middleware0)
|
||||
assert.Len(t, router.autoRedirect, 2)
|
||||
assert.Len(t, router.allAutoRedirect, 2)
|
||||
compareFunc(t, router.autoRedirect[0], middleware1)
|
||||
compareFunc(t, router.allAutoRedirect[0], middleware1)
|
||||
compareFunc(t, router.autoRedirect[1], middleware0)
|
||||
compareFunc(t, router.allAutoRedirect[1], middleware0)
|
||||
}
|
||||
|
||||
func TestAutoRedirectWithGlobalHandlers(t *testing.T) {
|
||||
var middleware0 HandlerFunc = func(c *Context) {}
|
||||
var middleware1 HandlerFunc = func(c *Context) {}
|
||||
var middleware2 HandlerFunc = func(c *Context) {}
|
||||
|
||||
router := New()
|
||||
router.Use(middleware2)
|
||||
|
||||
router.AutoRedirect(middleware0)
|
||||
assert.Len(t, router.allAutoRedirect, 2)
|
||||
assert.Len(t, router.Handlers, 1)
|
||||
assert.Len(t, router.autoRedirect, 1)
|
||||
|
||||
compareFunc(t, router.Handlers[0], middleware2)
|
||||
compareFunc(t, router.autoRedirect[0], middleware0)
|
||||
compareFunc(t, router.allAutoRedirect[0], middleware2)
|
||||
compareFunc(t, router.allAutoRedirect[1], middleware0)
|
||||
|
||||
router.Use(middleware1)
|
||||
assert.Len(t, router.allAutoRedirect, 3)
|
||||
assert.Len(t, router.Handlers, 2)
|
||||
assert.Len(t, router.autoRedirect, 1)
|
||||
|
||||
compareFunc(t, router.Handlers[0], middleware2)
|
||||
compareFunc(t, router.Handlers[1], middleware1)
|
||||
compareFunc(t, router.autoRedirect[0], middleware0)
|
||||
compareFunc(t, router.allAutoRedirect[0], middleware2)
|
||||
compareFunc(t, router.allAutoRedirect[1], middleware1)
|
||||
compareFunc(t, router.allAutoRedirect[2], middleware0)
|
||||
}
|
||||
|
||||
func compareFunc(t *testing.T, a, b any) {
|
||||
sf1 := reflect.ValueOf(a)
|
||||
sf2 := reflect.ValueOf(b)
|
||||
@ -913,3 +966,102 @@ func TestMethodNotAllowedNoRoute(t *testing.T) {
|
||||
assert.NotPanics(t, func() { g.ServeHTTP(resp, req) })
|
||||
assert.Equal(t, http.StatusNotFound, resp.Code)
|
||||
}
|
||||
|
||||
// Test the fix for https://github.com/gin-gonic/gin/pull/4415
|
||||
func TestLiteralColonWithRun(t *testing.T) {
|
||||
SetMode(TestMode)
|
||||
router := New()
|
||||
|
||||
router.GET(`/test\:action`, func(c *Context) {
|
||||
c.JSON(http.StatusOK, H{"path": "literal_colon"})
|
||||
})
|
||||
|
||||
router.updateRouteTrees()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "literal_colon")
|
||||
}
|
||||
|
||||
func TestLiteralColonWithDirectServeHTTP(t *testing.T) {
|
||||
SetMode(TestMode)
|
||||
router := New()
|
||||
|
||||
router.GET(`/test\:action`, func(c *Context) {
|
||||
c.JSON(http.StatusOK, H{"path": "literal_colon"})
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "literal_colon")
|
||||
}
|
||||
|
||||
func TestLiteralColonWithHandler(t *testing.T) {
|
||||
SetMode(TestMode)
|
||||
router := New()
|
||||
|
||||
router.GET(`/test\:action`, func(c *Context) {
|
||||
c.JSON(http.StatusOK, H{"path": "literal_colon"})
|
||||
})
|
||||
|
||||
handler := router.Handler()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "literal_colon")
|
||||
}
|
||||
|
||||
func TestLiteralColonWithHTTPServer(t *testing.T) {
|
||||
SetMode(TestMode)
|
||||
router := New()
|
||||
|
||||
router.GET(`/test\:action`, func(c *Context) {
|
||||
c.JSON(http.StatusOK, H{"path": "literal_colon"})
|
||||
})
|
||||
|
||||
router.GET("/test/:param", func(c *Context) {
|
||||
c.JSON(http.StatusOK, H{"param": c.Param("param")})
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "literal_colon")
|
||||
|
||||
w2 := httptest.NewRecorder()
|
||||
req2, _ := http.NewRequest(http.MethodGet, "/test/foo", nil)
|
||||
router.ServeHTTP(w2, req2)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w2.Code)
|
||||
assert.Contains(t, w2.Body.String(), "foo")
|
||||
}
|
||||
|
||||
// Test that updateRouteTrees is called only once
|
||||
func TestUpdateRouteTreesCalledOnce(t *testing.T) {
|
||||
SetMode(TestMode)
|
||||
router := New()
|
||||
|
||||
router.GET(`/test\:action`, func(c *Context) {
|
||||
c.String(http.StatusOK, "ok")
|
||||
})
|
||||
|
||||
for range 5 {
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "ok", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,6 +273,27 @@ func TestRouteRedirectFixedPath(t *testing.T) {
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, w.Code)
|
||||
}
|
||||
|
||||
func TestRouteRedirectWithHandler(t *testing.T) {
|
||||
router := New()
|
||||
router.RedirectTrailingSlash = true
|
||||
router.GET("/path", func(c *Context) {})
|
||||
passed := []bool{false, false}
|
||||
router.Use(func(c *Context) {
|
||||
passed[0] = true
|
||||
c.Next()
|
||||
})
|
||||
router.AutoRedirect(func(c *Context) {
|
||||
passed[1] = true
|
||||
c.Next()
|
||||
})
|
||||
|
||||
w := performRequest(router, http.MethodGet, "/path/")
|
||||
assert.Equal(t, "/path", w.Header().Get("Location"))
|
||||
assert.Equal(t, http.StatusMovedPermanently, w.Code)
|
||||
assert.True(t, passed[0])
|
||||
assert.True(t, passed[1])
|
||||
}
|
||||
|
||||
// TestContextParamsGet tests that a parameter can be parsed from the URL.
|
||||
func TestRouteParamsByName(t *testing.T) {
|
||||
name := ""
|
||||
|
||||
13
tree.go
13
tree.go
@ -5,6 +5,7 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode"
|
||||
@ -77,14 +78,22 @@ func (n *node) addChild(child *node) {
|
||||
}
|
||||
}
|
||||
|
||||
// safeUint16 converts int to uint16 safely, capping at math.MaxUint16
|
||||
func safeUint16(n int) uint16 {
|
||||
if n > math.MaxUint16 {
|
||||
return math.MaxUint16
|
||||
}
|
||||
return uint16(n)
|
||||
}
|
||||
|
||||
func countParams(path string) uint16 {
|
||||
colons := strings.Count(path, ":")
|
||||
stars := strings.Count(path, "*")
|
||||
return uint16(colons + stars)
|
||||
return safeUint16(colons + stars)
|
||||
}
|
||||
|
||||
func countSections(path string) uint16 {
|
||||
return uint16(strings.Count(path, "/"))
|
||||
return safeUint16(strings.Count(path, "/"))
|
||||
}
|
||||
|
||||
type nodeType uint8
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user