mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-16 13:22:09 +08:00
feat: user-configurable querycache and formcache
This commit is contained in:
parent
d16fdb15fa
commit
6d7e535a30
49
context.go
49
context.go
@ -456,13 +456,26 @@ func (c *Context) QueryArray(key string) (values []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) initQueryCache() {
|
func (c *Context) initQueryCache() {
|
||||||
if c.queryCache == nil {
|
if !c.engine.cacheConfig.EnableQueryCache || c.queryCache != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if c.Request != nil {
|
if c.Request != nil {
|
||||||
c.queryCache = c.Request.URL.Query()
|
c.queryCache = c.Request.URL.Query()
|
||||||
} else {
|
} else {
|
||||||
c.queryCache = url.Values{}
|
c.queryCache = url.Values{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetQuery sets the values for a given query key in the context's query cache.
|
||||||
|
// It ensures that the query cache is initialized before setting the values.
|
||||||
|
// If the query key already exists, its values will be overwritten with the provided values.
|
||||||
|
func (c *Context) SetQuery(key string, values []string) {
|
||||||
|
if !c.engine.cacheConfig.EnableQueryCache {
|
||||||
|
return // If query caching is disabled, just return without modifying the cache
|
||||||
|
}
|
||||||
|
c.initQueryCache()
|
||||||
|
c.queryCache[key] = values
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetQueryArray returns a slice of strings for a given query key, plus
|
// GetQueryArray returns a slice of strings for a given query key, plus
|
||||||
@ -526,16 +539,42 @@ func (c *Context) PostFormArray(key string) (values []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) initFormCache() {
|
func (c *Context) initFormCache() {
|
||||||
if c.formCache == nil {
|
if !c.engine.cacheConfig.EnableFormCache || c.formCache != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Request == nil {
|
||||||
|
return // If the Request is nil, exit early to avoid a nil pointer dereference
|
||||||
|
}
|
||||||
|
|
||||||
c.formCache = make(url.Values)
|
c.formCache = make(url.Values)
|
||||||
req := c.Request
|
if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
|
||||||
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
|
|
||||||
if !errors.Is(err, http.ErrNotMultipart) {
|
if !errors.Is(err, http.ErrNotMultipart) {
|
||||||
debugPrint("error on parse multipart form array: %v", err)
|
debugPrint("error on parse multipart form array: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.formCache = req.PostForm
|
c.formCache = c.Request.PostForm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetForm sets the values for a given form key in the context's form cache.
|
||||||
|
// It ensures that the form cache is initialized before setting the values.
|
||||||
|
// If the form key already exists, its values will be overwritten with the provided values.
|
||||||
|
func (c *Context) SetForm(key string, values []string) {
|
||||||
|
c.initFormCache()
|
||||||
|
if c.formCache != nil { // Only set values if the form cache is enabled
|
||||||
|
c.formCache[key] = values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetForm retrieves the values associated with a given form key from the context's form cache.
|
||||||
|
// If the form cache has not been initialized, it does so first.
|
||||||
|
// If the form key does not exist, an empty slice is returned.
|
||||||
|
func (c *Context) GetForm(key string) []string {
|
||||||
|
c.initFormCache()
|
||||||
|
if c.formCache == nil {
|
||||||
|
return []string{} // Return an empty slice when the form cache is disabled
|
||||||
|
}
|
||||||
|
return c.formCache[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostFormArray returns a slice of strings for a given form key, plus
|
// GetPostFormArray returns a slice of strings for a given form key, plus
|
||||||
|
170
context_test.go
170
context_test.go
@ -521,6 +521,65 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
|||||||
assert.Equal(t, 0, len(dicts))
|
assert.Equal(t, 0, len(dicts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetQuery(t *testing.T) {
|
||||||
|
// Create a Context instance with initialized query cache
|
||||||
|
c := &Context{
|
||||||
|
engine: &Engine{
|
||||||
|
cacheConfig: NewCacheConfig(true, true),
|
||||||
|
},
|
||||||
|
Request: &http.Request{
|
||||||
|
URL: &url.URL{
|
||||||
|
RawQuery: "existingKey=value1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key and values to set
|
||||||
|
key := "testKey"
|
||||||
|
values := []string{"value1", "value2"}
|
||||||
|
|
||||||
|
// Call SetQuery method
|
||||||
|
c.SetQuery(key, values)
|
||||||
|
|
||||||
|
// Retrieve values from the query cache
|
||||||
|
retrievedValues, ok := c.queryCache[key]
|
||||||
|
|
||||||
|
// Check if the values were correctly set
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Key %s not found in query cache", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(retrievedValues, values) {
|
||||||
|
t.Errorf("Expected values %v, got %v", values, retrievedValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetQueryWithCacheDisabled(t *testing.T) {
|
||||||
|
// Create a Context instance with query cache disabled
|
||||||
|
c := &Context{
|
||||||
|
engine: &Engine{
|
||||||
|
cacheConfig: NewCacheConfig(false, false), // Disabling both query and form caches
|
||||||
|
},
|
||||||
|
Request: &http.Request{
|
||||||
|
URL: &url.URL{
|
||||||
|
RawQuery: "existingKey=value1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key and values to set
|
||||||
|
key := "testKey"
|
||||||
|
values := []string{"value1", "value2"}
|
||||||
|
|
||||||
|
// Call SetQuery method
|
||||||
|
c.SetQuery(key, values)
|
||||||
|
|
||||||
|
// Since the query cache is disabled, we expect the query cache to be nil
|
||||||
|
if c.queryCache != nil {
|
||||||
|
t.Errorf("Expected query cache to be nil, got %v", c.queryCache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextPostFormMultipart(t *testing.T) {
|
func TestContextPostFormMultipart(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.Request = createMultipartRequest()
|
c.Request = createMultipartRequest()
|
||||||
@ -613,6 +672,117 @@ func TestContextPostFormMultipart(t *testing.T) {
|
|||||||
assert.Equal(t, 0, len(dicts))
|
assert.Equal(t, 0, len(dicts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetForm(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest("POST", "/", nil)
|
||||||
|
c := &Context{
|
||||||
|
Request: req,
|
||||||
|
engine: &Engine{
|
||||||
|
cacheConfig: NewCacheConfig(true, true), // Enabling both query and form caches
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
key := "testKey"
|
||||||
|
values := []string{"value1", "value2"}
|
||||||
|
|
||||||
|
c.SetForm(key, values)
|
||||||
|
retrievedValues := c.formCache[key]
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(values, retrievedValues) {
|
||||||
|
t.Errorf("Expected values %v, got %v", values, retrievedValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetForm(t *testing.T) {
|
||||||
|
// Create a Context instance with initialized form cache
|
||||||
|
c := &Context{
|
||||||
|
engine: &Engine{
|
||||||
|
cacheConfig: NewCacheConfig(true, true), // Assuming this initializes both caches
|
||||||
|
},
|
||||||
|
formCache: map[string][]string{
|
||||||
|
"existingKey": {"existingValue"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test retrieval of existing key
|
||||||
|
existingKey := "existingKey"
|
||||||
|
retrievedValues := c.GetForm(existingKey)
|
||||||
|
if !reflect.DeepEqual(retrievedValues, []string{"existingValue"}) {
|
||||||
|
t.Errorf("Expected values %v, got %v", []string{"existingValue"}, retrievedValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test retrieval of non-existing key
|
||||||
|
nonExistingKey := "nonExistingKey"
|
||||||
|
retrievedValues = c.GetForm(nonExistingKey)
|
||||||
|
if len(retrievedValues) != 0 {
|
||||||
|
t.Errorf("Expected an empty slice, got %v", retrievedValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetFormWithCacheDisabled(t *testing.T) {
|
||||||
|
c := &Context{
|
||||||
|
engine: &Engine{
|
||||||
|
cacheConfig: NewCacheConfig(false, false), // Disabling both query and form caches
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
key := "testKey"
|
||||||
|
values := []string{"value1", "value2"}
|
||||||
|
|
||||||
|
c.SetForm(key, values)
|
||||||
|
|
||||||
|
// Since the form cache is disabled, we expect the form cache to be nil
|
||||||
|
if c.formCache != nil {
|
||||||
|
t.Errorf("Expected form cache to be nil, got %v", c.formCache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFormWithCacheDisabled(t *testing.T) {
|
||||||
|
c := &Context{
|
||||||
|
engine: &Engine{
|
||||||
|
cacheConfig: NewCacheConfig(false, false), // Disabling both query and form caches
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
key := "existingKey"
|
||||||
|
|
||||||
|
// Call GetForm method
|
||||||
|
retrievedValues := c.GetForm(key)
|
||||||
|
|
||||||
|
// Since the form cache is disabled and no pre-existing values were set, we expect the retrieved values to be an empty slice
|
||||||
|
if len(retrievedValues) != 0 {
|
||||||
|
t.Errorf("Expected an empty slice, got %v", retrievedValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChangeUrlParam(t *testing.T) {
|
||||||
|
ChangeUrlParam := func(c *Context) {
|
||||||
|
params, _ := url.ParseQuery(c.Request.URL.RawQuery)
|
||||||
|
params.Set("xxx", "yyy")
|
||||||
|
c.Request.URL.RawQuery = params.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
r := Default()
|
||||||
|
r.Use(ChangeUrlParam)
|
||||||
|
r.GET("/test", func(c *Context) {
|
||||||
|
// Dummy handler
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a test request with original parameters
|
||||||
|
req, err := http.NewRequest("GET", "/test?param1=value1", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the response
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
// Check that the URL parameter "xxx" has been set to "yyy"
|
||||||
|
if got := req.URL.Query().Get("xxx"); got != "yyy" {
|
||||||
|
t.Errorf("ChangeUrlParam() = %v, want %v", got, "yyy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextSetCookie(t *testing.T) {
|
func TestContextSetCookie(t *testing.T) {
|
||||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||||
c.SetSameSite(http.SameSiteLaxMode)
|
c.SetSameSite(http.SameSiteLaxMode)
|
||||||
|
22
gin.go
22
gin.go
@ -79,6 +79,22 @@ const (
|
|||||||
PlatformCloudflare = "CF-Connecting-IP"
|
PlatformCloudflare = "CF-Connecting-IP"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CacheConfig represents the configuration options for managing query and form caches
|
||||||
|
// within a Gin context. It provides flags to enable or disable the caching mechanisms
|
||||||
|
// for query parameters and form data, allowing for greater control over caching behavior.
|
||||||
|
type CacheConfig struct {
|
||||||
|
EnableQueryCache bool
|
||||||
|
EnableFormCache bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCacheConfig returns a new instance of CacheConfig.
|
||||||
|
func NewCacheConfig(enableQueryCache, enableFormCache bool) CacheConfig {
|
||||||
|
return CacheConfig{
|
||||||
|
EnableQueryCache: enableQueryCache,
|
||||||
|
EnableFormCache: enableFormCache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||||
// Create an instance of Engine, by using New() or Default()
|
// Create an instance of Engine, by using New() or Default()
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
@ -168,6 +184,7 @@ type Engine struct {
|
|||||||
maxSections uint16
|
maxSections uint16
|
||||||
trustedProxies []string
|
trustedProxies []string
|
||||||
trustedCIDRs []*net.IPNet
|
trustedCIDRs []*net.IPNet
|
||||||
|
cacheConfig CacheConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ IRouter = (*Engine)(nil)
|
var _ IRouter = (*Engine)(nil)
|
||||||
@ -204,6 +221,7 @@ func New() *Engine {
|
|||||||
secureJSONPrefix: "while(1);",
|
secureJSONPrefix: "while(1);",
|
||||||
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
||||||
trustedCIDRs: defaultTrustedCIDRs,
|
trustedCIDRs: defaultTrustedCIDRs,
|
||||||
|
cacheConfig: CacheConfig{true, true},
|
||||||
}
|
}
|
||||||
engine.RouterGroup.engine = engine
|
engine.RouterGroup.engine = engine
|
||||||
engine.pool.New = func() any {
|
engine.pool.New = func() any {
|
||||||
@ -649,6 +667,10 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
|||||||
serveError(c, http.StatusNotFound, default404Body)
|
serveError(c, http.StatusNotFound, default404Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) SetCacheConfig(config CacheConfig) {
|
||||||
|
engine.cacheConfig = config
|
||||||
|
}
|
||||||
|
|
||||||
var mimePlain = []string{MIMEPlain}
|
var mimePlain = []string{MIMEPlain}
|
||||||
|
|
||||||
func serveError(c *Context, code int, defaultMessage []byte) {
|
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user