mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-16 05:16:35 +08:00
feat: user-configurable querycache and formcache
This commit is contained in:
parent
d16fdb15fa
commit
6d7e535a30
69
context.go
69
context.go
@ -456,13 +456,26 @@ func (c *Context) QueryArray(key string) (values []string) {
|
||||
}
|
||||
|
||||
func (c *Context) initQueryCache() {
|
||||
if c.queryCache == nil {
|
||||
if c.Request != nil {
|
||||
c.queryCache = c.Request.URL.Query()
|
||||
} else {
|
||||
c.queryCache = url.Values{}
|
||||
}
|
||||
if !c.engine.cacheConfig.EnableQueryCache || c.queryCache != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.Request != nil {
|
||||
c.queryCache = c.Request.URL.Query()
|
||||
} else {
|
||||
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
|
||||
@ -526,16 +539,42 @@ func (c *Context) PostFormArray(key string) (values []string) {
|
||||
}
|
||||
|
||||
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 !errors.Is(err, http.ErrNotMultipart) {
|
||||
debugPrint("error on parse multipart form array: %v", err)
|
||||
}
|
||||
}
|
||||
c.formCache = req.PostForm
|
||||
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)
|
||||
if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
|
||||
if !errors.Is(err, http.ErrNotMultipart) {
|
||||
debugPrint("error on parse multipart form array: %v", err)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
170
context_test.go
170
context_test.go
@ -521,6 +521,65 @@ func TestContextQueryAndPostForm(t *testing.T) {
|
||||
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) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.Request = createMultipartRequest()
|
||||
@ -613,6 +672,117 @@ func TestContextPostFormMultipart(t *testing.T) {
|
||||
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) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c.SetSameSite(http.SameSiteLaxMode)
|
||||
|
22
gin.go
22
gin.go
@ -79,6 +79,22 @@ const (
|
||||
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.
|
||||
// Create an instance of Engine, by using New() or Default()
|
||||
type Engine struct {
|
||||
@ -168,6 +184,7 @@ type Engine struct {
|
||||
maxSections uint16
|
||||
trustedProxies []string
|
||||
trustedCIDRs []*net.IPNet
|
||||
cacheConfig CacheConfig
|
||||
}
|
||||
|
||||
var _ IRouter = (*Engine)(nil)
|
||||
@ -204,6 +221,7 @@ func New() *Engine {
|
||||
secureJSONPrefix: "while(1);",
|
||||
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
||||
trustedCIDRs: defaultTrustedCIDRs,
|
||||
cacheConfig: CacheConfig{true, true},
|
||||
}
|
||||
engine.RouterGroup.engine = engine
|
||||
engine.pool.New = func() any {
|
||||
@ -649,6 +667,10 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
serveError(c, http.StatusNotFound, default404Body)
|
||||
}
|
||||
|
||||
func (engine *Engine) SetCacheConfig(config CacheConfig) {
|
||||
engine.cacheConfig = config
|
||||
}
|
||||
|
||||
var mimePlain = []string{MIMEPlain}
|
||||
|
||||
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user