Merge b8e1de3745a9520649676c906ec8103d5edc32b6 into dcaa4296d111981ffb31ac3eba90bb63e1eb5ab9

This commit is contained in:
Alessandro (Ale) Segala 2026-08-16 17:37:09 -07:00 committed by GitHub
commit c50e0d90ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 46 additions and 0 deletions

View File

@ -588,6 +588,11 @@ 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) (values []string, ok bool) {
if c.Request == nil || c.Request.URL == nil || c.Request.URL.RawQuery == "" {
// Fast path to save unnecessary allocations when the query string is empty
return nil, false
}
c.initQueryCache()
values, ok = c.queryCache[key]
return
@ -602,6 +607,11 @@ func (c *Context) QueryMap(key string) (dicts map[string]string) {
// GetQueryMap returns a map for a given query key, plus a boolean value
// whether at least one value exists for the given key.
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
if c.Request == nil || c.Request.URL == nil || c.Request.URL.RawQuery == "" {
// Fast path to save unnecessary allocations when the query string is empty
return nil, false
}
c.initQueryCache()
return getMapFromFormData(c.queryCache, key)
}

View File

@ -846,9 +846,38 @@ func TestContextQuery(t *testing.T) {
assert.Empty(t, c.PostForm("foo"))
}
func TestContextQueryAllocations(t *testing.T) {
// URLs with empty query strings should not cause allocations
c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest(http.MethodGet, "http://example.com/", nil)
allocs := testing.AllocsPerRun(100, func() {
// Reset the query cache before each run because testing.AllocsPerRun does a warm-up for each test
c.queryCache = nil
_, _ = c.GetQuery("foo")
})
assert.Zero(t, allocs, "expected GetQuery to not cause allocations with empty query strings")
allocs = testing.AllocsPerRun(100, func() {
// Reset the query cache before each run because testing.AllocsPerRun does a warm-up for each test
c.queryCache = nil
_, _ = c.GetQueryArray("foo")
})
assert.Zero(t, allocs, "expected GetQueryArray to not cause allocations with empty query strings")
allocs = testing.AllocsPerRun(100, func() {
// Reset the query cache before each run because testing.AllocsPerRun does a warm-up for each test
c.queryCache = nil
_, _ = c.GetQueryMap("foo")
})
assert.Zero(t, allocs, "expected GetQueryMap to not cause allocations with empty query strings")
}
func TestContextInitQueryCache(t *testing.T) {
validURL, err := url.Parse("https://github.com/gin-gonic/gin/pull/3969?key=value&otherkey=othervalue")
require.NoError(t, err)
noQSURL, err := url.Parse("https://github.com/gin-gonic/gin/pull/3969")
require.NoError(t, err)
tests := []struct {
testName string
@ -873,6 +902,13 @@ func TestContextInitQueryCache(t *testing.T) {
testContext: &Context{Request: &http.Request{URL: nil}}, // explicit nil for readability
expectedQueryCache: url.Values{},
},
{
testName: "queryCache should be empty when URL has no query string",
testContext: &Context{
Request: &http.Request{URL: noQSURL},
},
expectedQueryCache: url.Values{},
},
{
testName: "queryCache should be populated when it not yet populated and Request + Request.URL are non nil",
testContext: &Context{Request: &http.Request{URL: validURL}}, // explicit nil for readability