Cache

cache is a middleware for caching HTTP Responses, which helps to improve the concurrent access capacity of the Server. Hertz also provides the adaptation of cache, supporting multi-backend, referring to gin-cache.

Install

go get github.com/hertz-contrib/cache

Import

import "github.com/hertz-contrib/cache"

Example

  • memory
func main() {
    h := server.New()
    // sets the global TTL value for items in the cache, which can be overridden at the item level
    memoryStore := persist.NewMemoryStore(1 * time.Minute)
    // sets the TTL value for URI-based items in the cache
    h.Use(cache.NewCacheByRequestURI(memoryStore, 2*time.Second))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })

    h.Spin()
}
  • redis
func main() {
    h := server.New()

    redisStore := persist.NewRedisStore(redis.NewClient(&redis.Options{
        Network: "tcp",
        Addr:    "127.0.0.1:6379",
    }))

    h.Use(cache.NewCacheByRequestURI(redisStore, 2*time.Second))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })

    h.Spin()
}

Initialization

cache provides three ways of initialization.

NewCacheByRequestURI

NewCacheByRequestURI is used to create middleware that caches response results with URI as the key.

Function Signature:

func NewCacheByRequestURI(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, opts ...Option) app.HandlerFunc

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    h.Use(cache.NewCacheByRequestURI(memoryStore, 2*time.Second))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })
    h.Spin()
}

NewCacheByRequestPath

NewCacheByRequestPath is used to create middleware that caches response results with URL as the key, discarding the query parameter.

Function Signature:

func NewCacheByRequestPath(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, opts ...Option) app.HandlerFunc

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    h.Use(cache.NewCacheByRequestPath(memoryStore, 2*time.Second))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })
    h.Spin()
}

NewCache

NewCache is used to create middleware for custom cache logic, and the cache key must be declared manually (required in conjunction with WithCacheStrategyByRequest).

Function Signature:

func NewCache(
    defaultCacheStore persist.CacheStore,
    defaultExpire time.Duration,
    opts ...Option,
) app.HandlerFunc

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    h.Use(cache.NewCache(
        memoryStore,
        2*time.Second,
        cache.WithCacheStrategyByRequest(func(ctx context.Context, c *app.RequestContext) (bool, cache.Strategy) {
            return true, cache.Strategy{
                CacheKey: c.Request.URI().String(),
            }
        }),
    ))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })

    h.Spin()
}

Configuration

Configuration Default Description
WithCacheStrategyByRequest nil Used to set custom caching policies
WithOnHitCache nil Used to set the callback function after a cache hits
WithOnMissCache nil Used to set the callback function for cache misses
WithBeforeReplyWithCache nil Used to set the callback function before returning the cached response
WithOnShareSingleFlight nil Used to set the callback function when the result of a SingleFlight is shared by the request
WithSingleFlightForgetTimeout 0 Used to set the timeout for SingleFlight
WithIgnoreQueryOrder false Used to set the order in which query parameters are ignored when using a URI as the cached Key
WithPrefixKey "" Used to set the prefix of the cache response key
WithoutHeader false Used to set whether response headers need to be cached

WithCacheStrategyByRequest

Customize the cache policy by using WithCacheStrategyByRequest, including the cache key, storage medium, and expiration time.

This configuration assumes that the cache middleware is initialized via the cache.NewCache method.

Function Signature:

func WithCacheStrategyByRequest(getGetCacheStrategyByRequest GetCacheStrategyByRequest) Option

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    h.Use(cache.NewCache(
        memoryStore,
        2*time.Second,
        cache.WithCacheStrategyByRequest(func(ctx context.Context, c *app.RequestContext) (bool, cache.Strategy) {
            return true, cache.Strategy{
                CacheKey: c.Request.URI().String(),
            }
        }),
    ))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })
    
    h.Spin()
}

WithOnHitCache & WithOnMissCache

Set the callback function for cache hits by using WithOnHitCache.

Set the callback function for cache misses by using WithOnMissCache.

Function Signature:

func WithOnHitCache(cb OnHitCacheCallback) Option

func WithOnMissCache(cb OnMissCacheCallback) Option

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    var cacheHitCount, cacheMissCount int32

    h.Use(cache.NewCacheByRequestURI(
        memoryStore,
        2*time.Second,
        cache.WithOnHitCache(func(ctx context.Context, c *app.RequestContext) {
            atomic.AddInt32(&cacheHitCount, 1)
        }),
        cache.WithOnMissCache(func(ctx context.Context, c *app.RequestContext) {
            atomic.AddInt32(&cacheMissCount, 1)
        }),
    ))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })
    h.GET("/get_hit_count", func(ctx context.Context, c *app.RequestContext) {
        c.String(200, fmt.Sprintf("total hit count: %d", cacheHitCount))
    })
    h.GET("/get_miss_count", func(ctx context.Context, c *app.RequestContext) {
        c.String(200, fmt.Sprintf("total miss count: %d", cacheMissCount))
    })

    h.Spin()
}

WithBeforeReplyWithCache

Set the callback function before returning the cached response by using WithBeforeReplyWithCache.

Function Signature:

func WithBeforeReplyWithCache(cb BeforeReplyWithCacheCallback) Option

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    h.Use(cache.NewCacheByRequestURI(
        memoryStore,
        2*time.Second,
        cache.WithBeforeReplyWithCache(func(c *app.RequestContext, cache *cache.ResponseCache) {
            cache.Data = append([]byte{'p', 'r', 'e', 'f', 'i', 'x', '-'}, cache.Data...)
        }),
    ))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })

    h.Spin()
}

WithOnShareSingleFlight & WithSingleFlightForgetTimeout

Set the callback function when a SingleFlight result is requested to be shared by using WithOnShareSingleFlight.

Set the timeout for SingleFlight by using WithSingleFlightForgetTimeout.

Function Signature:

func WithOnShareSingleFlight(cb OnShareSingleFlightCallback) Option

func WithSingleFlightForgetTimeout(forgetTimeout time.Duration) Option

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    h.Use(cache.NewCacheByRequestPath(
        memoryStore,
        10*time.Second,
        cache.WithOnShareSingleFlight(func(ctx context.Context, c *app.RequestContext) {
            hlog.Info("share the singleFlight result " + string(c.Response.Body()))
        }),
        cache.WithSingleFlightForgetTimeout(1*time.Second),
    ))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        time.Sleep(3 * time.Second)
        c.String(http.StatusOK, "hello world")
    })

    h.Spin()
}

WithIgnoreQueryOrder

Set the query parameter order of the URI to be ignored when creating a cache middleware using the NewCacheByRequestURI method by using WithIgnoreQueryOrder.

Function Signature:

func WithIgnoreQueryOrder(b bool) Option

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    h.Use(cache.NewCacheByRequestPath(
        memoryStore,
        60*time.Second,
        cache.WithIgnoreQueryOrder(true),
        cache.WithOnHitCache(func(c context.Context, ctx *app.RequestContext) {
            hlog.Infof("hit cache IgnoreQueryOrder")
        }),
        cache.WithOnMissCache(func(c context.Context, ctx *app.RequestContext) {
            hlog.Infof("miss cache IgnoreQueryOrder")
        }),
    ))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })
    
    h.Spin()
}

WithPrefixKey

Set the prefix of the response key by using WithPrefixKey.

Function Signature:

func WithPrefixKey(prefix string) Option

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    h.Use(cache.NewCache(
        memoryStore,
        60*time.Second,
        cache.WithPrefixKey("prefix-"),
        cache.WithOnHitCache(func(c context.Context, ctx *app.RequestContext) {
            resp := &cache.ResponseCache{}
            memoryStore.Get(c, "prefix-test", &resp)
            hlog.Info("data = " + string(resp.Data))
        }),
        cache.WithCacheStrategyByRequest(func(ctx context.Context, c *app.RequestContext) (bool, cache.Strategy) {
            return true, cache.Strategy{
                CacheKey: "test",
            }
        }),
    ))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })
    
    h.Spin()
}

WithoutHeader

Set whether the response header should be cached by using WithoutHeader, or cache the response header if it is false.

Function Signature:

func WithoutHeader(b bool) Option

Sample Code:

func main() {
    h := server.New()

    memoryStore := persist.NewMemoryStore(1 * time.Minute)

    h.Use(cache.NewCache(
        memoryStore,
        60*time.Second,
        cache.WithoutHeader(true),
        cache.WithCacheStrategyByRequest(func(ctx context.Context, c *app.RequestContext) (bool, cache.Strategy) {
            return true, cache.Strategy{
                CacheKey: "test-key",
            }
        }),
        cache.WithOnHitCache(func(c context.Context, ctx *app.RequestContext) {
            resp := &cache.ResponseCache{}
            memoryStore.Get(c, "test-key", &resp)
            hlog.Info("header = " + string(resp.Header.Get("head")))
            hlog.Info("data = " + string(resp.Data))
        }),
    ))
    h.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        c.String(http.StatusOK, "hello world")
    })
    
    h.Spin()
}

Full Example

Refer to the cache/example for full usage examples.