Skip to content

Commit

Permalink
Update the code logic for latestNode in tree.go (gin-gonic#2897)
Browse files Browse the repository at this point in the history
(cherry picked from commit 3fe9289)
  • Loading branch information
zhuxi0511 authored and Bisstocuz committed Nov 22, 2021
1 parent 2320b87 commit 0f874bf
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 98 deletions.
115 changes: 67 additions & 48 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const (
// BodyBytesKey indicates a default body bytes key.
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"

const abortIndex int8 = math.MaxInt8 / 2
// abortIndex represents a typical value used in abort functions.
const abortIndex int8 = math.MaxInt8 >> 1

// 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.
Expand All @@ -54,8 +55,9 @@ type Context struct {
index int8
fullPath string

engine *Engine
params *Params
engine *Engine
params *Params
skippedNodes *[]skippedNode

// This mutex protect Keys map
mu sync.RWMutex
Expand Down Expand Up @@ -87,17 +89,18 @@ type Context struct {

func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[0:0]
c.Params = c.Params[:0]
c.handlers = nil
c.index = -1

c.fullPath = ""
c.Keys = nil
c.Errors = c.Errors[0:0]
c.Errors = c.Errors[:0]
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
*c.params = (*c.params)[0:0]
*c.params = (*c.params)[:0]
*c.skippedNodes = (*c.skippedNodes)[:0]
}

// Copy returns a copy of the current context that can be safely used outside the request's scope.
Expand Down Expand Up @@ -219,7 +222,8 @@ func (c *Context) Error(err error) *Error {
panic("err is nil")
}

parsedError, ok := err.(*Error)
var parsedError *Error
ok := errors.As(err, &parsedError)
if !ok {
parsedError = &Error{
Err: err,
Expand Down Expand Up @@ -382,6 +386,15 @@ func (c *Context) Param(key string) string {
return c.Params.ByName(key)
}

// AddParam adds param to context and
// replaces path param key with given value for e2e testing purposes
// Example Route: "/user/:id"
// AddParam("id", 1)
// Result: "/user/1"
func (c *Context) AddParam(key, value string) {
c.Params = append(c.Params, Param{Key: key, Value: value})
}

// Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
Expand All @@ -390,9 +403,9 @@ func (c *Context) Param(key string) string {
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
func (c *Context) Query(key string) string {
value, _ := c.GetQuery(key)
return value
func (c *Context) Query(key string) (value string) {
value, _ = c.GetQuery(key)
return
}

// DefaultQuery returns the keyed url query value if it exists,
Expand Down Expand Up @@ -426,9 +439,9 @@ func (c *Context) GetQuery(key string) (string, bool) {

// QueryArray returns a slice of strings for a given query key.
// The length of the slice depends on the number of params with the given key.
func (c *Context) QueryArray(key string) []string {
values, _ := c.GetQueryArray(key)
return values
func (c *Context) QueryArray(key string) (values []string) {
values, _ = c.GetQueryArray(key)
return
}

func (c *Context) initQueryCache() {
Expand All @@ -443,18 +456,16 @@ func (c *Context) initQueryCache() {

// GetQueryArray returns a slice of strings for a given query key, plus
// a boolean value whether at least one value exists for the given key.
func (c *Context) GetQueryArray(key string) ([]string, bool) {
func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
c.initQueryCache()
if values, ok := c.queryCache[key]; ok && len(values) > 0 {
return values, true
}
return []string{}, false
values, ok = c.queryCache[key]
return
}

// QueryMap returns a map for a given query key.
func (c *Context) QueryMap(key string) map[string]string {
dicts, _ := c.GetQueryMap(key)
return dicts
func (c *Context) QueryMap(key string) (dicts map[string]string) {
dicts, _ = c.GetQueryMap(key)
return
}

// GetQueryMap returns a map for a given query key, plus a boolean value
Expand All @@ -466,9 +477,9 @@ func (c *Context) GetQueryMap(key string) (map[string]string, bool) {

// PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string `("")`.
func (c *Context) PostForm(key string) string {
value, _ := c.GetPostForm(key)
return value
func (c *Context) PostForm(key string) (value string) {
value, _ = c.GetPostForm(key)
return
}

// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
Expand Down Expand Up @@ -497,17 +508,17 @@ func (c *Context) GetPostForm(key string) (string, bool) {

// PostFormArray returns a slice of strings for a given form key.
// The length of the slice depends on the number of params with the given key.
func (c *Context) PostFormArray(key string) []string {
values, _ := c.GetPostFormArray(key)
return values
func (c *Context) PostFormArray(key string) (values []string) {
values, _ = c.GetPostFormArray(key)
return
}

func (c *Context) initFormCache() {
if c.formCache == nil {
c.formCache = make(url.Values)
req := c.Request
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
if err != http.ErrNotMultipart {
if !errors.Is(err, http.ErrNotMultipart) {
debugPrint("error on parse multipart form array: %v", err)
}
}
Expand All @@ -517,18 +528,16 @@ func (c *Context) initFormCache() {

// GetPostFormArray returns a slice of strings for a given form key, plus
// a boolean value whether at least one value exists for the given key.
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
c.initFormCache()
if values := c.formCache[key]; len(values) > 0 {
return values, true
}
return []string{}, false
values, ok = c.formCache[key]
return
}

// PostFormMap returns a map for a given form key.
func (c *Context) PostFormMap(key string) map[string]string {
dicts, _ := c.GetPostFormMap(key)
return dicts
func (c *Context) PostFormMap(key string) (dicts map[string]string) {
dicts, _ = c.GetPostFormMap(key)
return
}

// GetPostFormMap returns a map for a given form key, plus a boolean value
Expand Down Expand Up @@ -1161,22 +1170,28 @@ func (c *Context) SetAccepted(formats ...string) {
/***** GOLANG.ORG/X/NET/CONTEXT *****/
/************************************/

// Deadline always returns that there is no deadline (ok==false),
// maybe you want to use Request.Context().Deadline() instead.
// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
func (c *Context) Deadline() (deadline time.Time, ok bool) {
return
if c.Request == nil || c.Request.Context() == nil {
return
}
return c.Request.Context().Deadline()
}

// Done always returns nil (chan which will wait forever),
// if you want to abort your work when the connection was closed
// you should use Request.Context().Done() instead.
// Done returns nil (chan which will wait forever) when c.Request has no Context.
func (c *Context) Done() <-chan struct{} {
return nil
if c.Request == nil || c.Request.Context() == nil {
return nil
}
return c.Request.Context().Done()
}

// Err always returns nil, maybe you want to use Request.Context().Err() instead.
// Err returns nil when c.Request has no Context.
func (c *Context) Err() error {
return nil
if c.Request == nil || c.Request.Context() == nil {
return nil
}
return c.Request.Context().Err()
}

// Value returns the value associated with this context for key, or nil
Expand All @@ -1187,8 +1202,12 @@ func (c *Context) Value(key interface{}) interface{} {
return c.Request
}
if keyAsString, ok := key.(string); ok {
val, _ := c.Get(keyAsString)
return val
if val, exists := c.Get(keyAsString); exists {
return val
}
}
return nil
if c.Request == nil || c.Request.Context() == nil {
return nil
}
return c.Request.Context().Value(key)
}
12 changes: 9 additions & 3 deletions gin.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ type Engine struct {
pool sync.Pool
trees methodTrees
maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
}
Expand Down Expand Up @@ -200,7 +201,8 @@ func Default() *Engine {

func (engine *Engine) allocateContext() *Context {
v := make(Params, 0, engine.maxParams)
return &Context{engine: engine, params: &v}
skippedNodes := make([]skippedNode, 0, engine.maxSections)
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
}

// Delims sets template left and right delims and returns a Engine instance.
Expand Down Expand Up @@ -306,6 +308,10 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}

if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
}

// Routes returns a slice of registered routes, including some useful information, such as:
Expand Down Expand Up @@ -539,7 +545,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, unescape)
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
Expand Down Expand Up @@ -567,7 +573,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
Expand Down
15 changes: 15 additions & 0 deletions gin_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,13 @@ func TestTreeRunDynamicRouting(t *testing.T) {
router.GET("/ab/*xx", func(c *Context) { c.String(http.StatusOK, "/ab/*xx") })
router.GET("/", func(c *Context) { c.String(http.StatusOK, "home") })
router.GET("/:cc", func(c *Context) { c.String(http.StatusOK, "/:cc") })
router.GET("/c1/:dd/e", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e") })
router.GET("/c1/:dd/e1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/e1") })
router.GET("/c1/:dd/f1", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f1") })
router.GET("/c1/:dd/f2", func(c *Context) { c.String(http.StatusOK, "/c1/:dd/f2") })
router.GET("/:cc/cc", func(c *Context) { c.String(http.StatusOK, "/:cc/cc") })
router.GET("/:cc/:dd/ee", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/ee") })
router.GET("/:cc/:dd/f", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/f") })
router.GET("/:cc/:dd/:ee/ff", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/ff") })
router.GET("/:cc/:dd/:ee/:ff/gg", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/gg") })
router.GET("/:cc/:dd/:ee/:ff/:gg/hh", func(c *Context) { c.String(http.StatusOK, "/:cc/:dd/:ee/:ff/:gg/hh") })
Expand Down Expand Up @@ -446,6 +451,10 @@ func TestTreeRunDynamicRouting(t *testing.T) {
testRequest(t, ts.URL+"/all", "", "/:cc")
testRequest(t, ts.URL+"/all/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/a/cc", "", "/:cc/cc")
testRequest(t, ts.URL+"/c1/d/e", "", "/c1/:dd/e")
testRequest(t, ts.URL+"/c1/d/e1", "", "/c1/:dd/e1")
testRequest(t, ts.URL+"/c1/d/ee", "", "/:cc/:dd/ee")
testRequest(t, ts.URL+"/c1/d/f", "", "/:cc/:dd/f")
testRequest(t, ts.URL+"/c/d/ee", "", "/:cc/:dd/ee")
testRequest(t, ts.URL+"/c/d/e/ff", "", "/:cc/:dd/:ee/ff")
testRequest(t, ts.URL+"/c/d/e/f/gg", "", "/:cc/:dd/:ee/:ff/gg")
Expand Down Expand Up @@ -528,6 +537,12 @@ func TestTreeRunDynamicRouting(t *testing.T) {
testRequest(t, ts.URL+"/get/abc/123abf/testss", "", "/get/abc/123abf/:param")
testRequest(t, ts.URL+"/get/abc/123abfff/te", "", "/get/abc/123abfff/:param")
// 404 not found
testRequest(t, ts.URL+"/c/d/e", "404 Not Found")
testRequest(t, ts.URL+"/c/d/e1", "404 Not Found")
testRequest(t, ts.URL+"/c/d/eee", "404 Not Found")
testRequest(t, ts.URL+"/c1/d/eee", "404 Not Found")
testRequest(t, ts.URL+"/c1/d/e2", "404 Not Found")
testRequest(t, ts.URL+"/cc/dd/ee/ff/gg/hh1", "404 Not Found")
testRequest(t, ts.URL+"/a/dd", "404 Not Found")
testRequest(t, ts.URL+"/addr/dd/aa", "404 Not Found")
testRequest(t, ts.URL+"/something/secondthing/121", "404 Not Found")
Expand Down
Loading

0 comments on commit 0f874bf

Please sign in to comment.