Code Monkey home page Code Monkey logo

gin-cache's Introduction

gin-cache

Release doc goreportcard for gin-cache codecov

English | 🇨🇳中文

A high performance gin middleware to cache http response. Compared to gin-contrib/cache. It has a huge performance improvement.

Feature

  • Has a huge performance improvement compared to gin-contrib/cache.
  • Cache http response in local memory or Redis.
  • Offer a way to custom the cache strategy by per request.
  • Use singleflight to avoid cache breakdown problem.
  • Only Cache 2xx HTTP Response.

How To Use

Install

go get -u github.com/chenyahui/gin-cache

Example

Cache In Local Memory

package main

import (
	"time"

	"github.com/chenyahui/gin-cache"
	"github.com/chenyahui/gin-cache/persist"
	"github.com/gin-gonic/gin"
)

func main() {
	app := gin.New()

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

	app.GET("/hello",
		cache.CacheByRequestURI(memoryStore, 2*time.Second),
		func(c *gin.Context) {
			c.String(200, "hello world")
		},
	)

	if err := app.Run(":8080"); err != nil {
		panic(err)
	}
}

Cache In Redis

package main

import (
	"time"

	"github.com/chenyahui/gin-cache"
	"github.com/chenyahui/gin-cache/persist"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
)

func main() {
	app := gin.New()

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

	app.GET("/hello",
		cache.CacheByRequestURI(redisStore, 2*time.Second),
		func(c *gin.Context) {
			c.String(200, "hello world")
		},
	)
	if err := app.Run(":8080"); err != nil {
		panic(err)
	}
}

Benchmark

wrk -c 500 -d 1m -t 5 http://127.0.0.1:8080/hello

MemoryStore

MemoryStore QPS

RedisStore

RedisStore QPS

gin-cache's People

Contributors

chenyahui avatar rts-gordon avatar vincentbernat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

gin-cache's Issues

Need a way to invalidate a key/reset cache when updates are made to the cached resources.

Need a way to invalidate a key/reset cache when updates are made to the cached resources.

For eg: if the resource is cached, and one of the resource properties is updated, we need to update the resource in the cache. Otherwise, the changes do not get reflected.

I am unsure if this is already possible, but if it is, an example of how to do this would greatly help.

set设置值问题(类型)

现在设置值会通过一个序列化方法变成二进制值 code 如果有需求是保存为字符串或者整型浮点型就不可用了,例如我要保存字符串asd,当前会保存为 \x0C\x00\x06asd。
我将原始函数改为SetByte现在满足需求,Set函数不做值类型的处理,是否还有其他好的实现方法。

func (store *RedisStore) Set(key string, value interface{}, expire time.Duration) error {
	ctx := context.TODO()
	return store.RedisClient.Set(ctx, key, value, expire).Err()
}

Cache without cookie

At the moment the cached content is also saved with Cookie/Session information witch is not ideal.
Is there a setting/option to cache only page content without headers or cookie info?

I want to go from this..
;����� ResponseCache�������Status����Header�����Data� �������Header���� ��� �������� ��T�������� Content-Type��application/json; charset=utf-8 Set-Cookie���mysession=MTY3MTI5MTE2MXxOd3dBTkZCSFZFRlhVVTVDU1VkVVZVSkxTemMxVUVVMFRsaEtWMHhLVUZreVRrZEpTMFZIUVVOVVFVZFFOa2xNVFRSVFdGUkxOa0U9fN2Qa-r2Lgq8UVXWfYlU6PsWZv3uCDHNVI-EB7xfS3ld; Path=/; Expires=Sat, 17 Dec 2022 16:32:41 GMT; Max-Age=3600; Secure; SameSite=Strict� {"count":10}

to this

{"count":10}

在使用singleflight, 对缓存的设置应该在singleflight里

在使用singleflight, 对缓存的设置应该在singleflight里吧, 当高并发,发生shared同一个key的缓存值,重复设置store, 不知道你这redis能不能抗的住.
缓存应该只缓存成功的返回.毕竟你不知道下游业务是如何出错的,错误的返回不应该进行缓存.
另外对日志的调用,作为一个库,最好提供接口, 并给可选项配置,供使用者选择合适的日志组件.
结合你的库和官方的库,我也作为相应简化和适配. 刚好项目有用,所以也做了个库,gin-cache

Is there a way to invalidate cache?

For example I put the cache middleware for a GET endpoint, but there's also a POST endpoint which supposed to be updating the results fetched from the GET endpoint. Therefore, whenever I call the POST endpoint, I would like whatever cache exists on the GET endpoint to be invalidated immediately so I could return the most up to date results.

Is there a way to accommodate this mechanism into gin-cache?

feat: option to cache until next hour

Would be great to have option for dynamic caching time.

E.g. I would like to cache until the next hour, if request comes at 9:01 AM, I'd like cache to expire at 10 AM.

Cache strategy based on some header

I would like to have different cache key based on the header that users set.
Is WithCacheStrategyByRequest() intended for this? I have trouble implementing this function. It looks like it's being overwritten in this lib.

This is my code

` var cacheStrategy cache.Option = cache.WithCacheStrategyByRequest(func(c *gin.Context) (bool, cache.Strategy) {
var key string = "yes"

	if 1 < 2 {
		key = "no"
	}

	return true, cache.Strategy{
		CacheKey: key,
	}
})

`

And I use it in CacheByRequestURI() but it never gets executed.

Example for options

Can you please provide example in the documentation how to use hitCacheCallback?

能否支持路由分组中使用?

能不能你下面这样使用,一整组都统一配置
如果可以,能否给个例子

v1Group := router.Group("/v1", cache.CachePageWithoutQuery(......))
{
	v1Group.GET("/a", ....)
	v1Group.GET("/b", ....)
}

Question about in-memory store choice

What was behind the decision in favour of jellydator/ttlcache over other libraries with high concurrency and unlimited capacity such as go-cache or bigcache for the in-memory store?

Also, are you planning to move to ttlcache v3?

What's the difference of time duration between persist and cache?

Hi there
There are two time duration of gin-cache: persist.NewMemoryStore(), cache.CacheByRequestURI, what's the difference between them?
Can they merge into one time duration?

memoryStore := persist.NewMemoryStore(1 * time.Minute)
	app.GET("/hello",
		cache.CacheByRequestURI(memoryStore, 2*time.Second),
		func(c *gin.Context) {
			c.String(200, "hello world")
		},
	)

Cross-origin information is cached

When there are multiple cross-domain domain names that allow cross-domain information, it will cause confusion when the cross-domain information is cached. It is hoped that the cross-domain return header can be ignored during caching

Support go-redis/redis/v9 client

Cannot use 'global.App.Redis' (type *"github.com/go-redis/redis/v9".Client) as the type *"github.com/go-redis/redis/v8".Client

本地用的 v9 版本的包,发现您包里没有对应的。

CacheStore interface missing context

Looking at the persist.CacheStore interface, it doesn't have a ctx context.Context as a function argument, which a potential issue when implementing Redis cache.

Currently, context.TODO() is being passed to the Redis client, which might be causing issues, if a request is cancelled during cache querying.

WriteHeader should only be called after Header().Write()

In this part of the code: https://github.com/chenyahui/gin-cache/blob/main/cache.go#L179-L185, c.Writer.WriteHeader(respCache.Status) is written before c.Writer.Header().Set(key, val).

However, based on the gin's documentation, Writer.Header().Set() will only take effect if it's written before Writer.WriteHeader.

type ResponseWriter interface {
	// Header returns the header map that will be sent by
	// WriteHeader. The Header map also is the mechanism with which
	// Handlers can set HTTP trailers.
	//
	// Changing the header map after a call to WriteHeader (or
	// Write) has no effect unless the modified headers are
	// trailers.
	//
	// There are two ways to set Trailers. The preferred way is to
	// predeclare in the headers which trailers you will later
	// send by setting the "Trailer" header to the names of the
	// trailer keys which will come later. In this case, those
	// keys of the Header map are treated as if they were
	// trailers. See the example. The second way, for trailer
	// keys not known to the Handler until after the first Write,
	// is to prefix the Header map keys with the TrailerPrefix
	// constant value. See TrailerPrefix.
	//
	// To suppress automatic response headers (such as "Date"), set
	// their value to nil.
	Header() Header

	// Write writes the data to the connection as part of an HTTP reply.
	//
	// If WriteHeader has not yet been called, Write calls
	// WriteHeader(http.StatusOK) before writing the data. If the Header
	// does not contain a Content-Type line, Write adds a Content-Type set
	// to the result of passing the initial 512 bytes of written data to
	// DetectContentType. Additionally, if the total size of all written
	// data is under a few KB and there are no Flush calls, the
	// Content-Length header is added automatically.
	//
	// Depending on the HTTP protocol version and the client, calling
	// Write or WriteHeader may prevent future reads on the
	// Request.Body. For HTTP/1.x requests, handlers should read any
	// needed request body data before writing the response. Once the
	// headers have been flushed (due to either an explicit Flusher.Flush
	// call or writing enough data to trigger a flush), the request body
	// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
	// handlers to continue to read the request body while concurrently
	// writing the response. However, such behavior may not be supported
	// by all HTTP/2 clients. Handlers should read before writing if
	// possible to maximize compatibility.
	Write([]byte) (int, error)

	// WriteHeader sends an HTTP response header with the provided
	// status code.
	//
	// If WriteHeader is not called explicitly, the first call to Write
	// will trigger an implicit WriteHeader(http.StatusOK).
	// Thus explicit calls to WriteHeader are mainly used to
	// send error codes.
	//
	// The provided code must be a valid HTTP 1xx-5xx status code.
	// Only one header may be written. Go does not currently
	// support sending user-defined 1xx informational headers,
	// with the exception of 100-continue response header that the
	// Server sends automatically when the Request.Body is read.
	WriteHeader(statusCode int)
}

Exception using both gin-cache and nanmu42/gzip middleware

package main

import (
	"strings"
	"time"

	cache "github.com/chenyahui/gin-cache"
	"github.com/chenyahui/gin-cache/persist"
	"github.com/gin-gonic/gin"
	"github.com/nanmu42/gzip"
)

func main() {
	app := gin.New()
	app.Use(gzip.DefaultHandler().Gin)
	memoryStore := persist.NewMemoryStore(1 * time.Minute)

	body := strings.Repeat("hello world", 100)
	app.GET("/hello",
		cache.CacheByRequestURI(memoryStore, 2*time.Second),
		func(c *gin.Context) {
			c.String(200, body)
		},
	)

	if err := app.Run(":8080"); err != nil {
		panic(err)
	}
}

Run multiple requests:

curl --compressed 127.0.0.1:8080/hello -v
curl --compressed 127.0.0.1:8080/hello -v
curl --compressed 127.0.0.1:8080/hello -v

Should get the error message:

# curl --compressed 127.0.0.1:8080/hello -v
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /hello HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Accept-Encoding: deflate, gzip, br
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Encoding: gzip
< Content-Type: text/plain; charset=utf-8
< Vary: Accept-Encoding
< Date: Sun, 04 Sep 2022 03:18:33 GMT
< Content-Length: 1100
<
* Error while processing content unencoding: incorrect header check
* Closing connection 0
curl: (61) Error while processing content unencoding: incorrect header check

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.