mirror of
https://github.com/kataras/iris.git
synced 2025-03-15 06:46:26 +01:00
[2] Continue working on #513
This commit is contained in:
parent
f634826346
commit
7eb520fc6b
202
cache.go
202
cache.go
|
@ -1,7 +1,6 @@
|
||||||
package iris
|
package iris
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -10,18 +9,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// CacheService is the cache service which caches the whole response body
|
// CacheServiceAPI describes the cache service which caches the whole response body
|
||||||
CacheService interface {
|
CacheServiceAPI interface {
|
||||||
// Start is the method which the CacheService starts the GC(check if expiration of each entry is passed , if yes then delete it from cache)
|
|
||||||
Start(time.Duration)
|
|
||||||
// Cache accepts a route's handler which will cache its response and a time.Duration(int64) which is the expiration duration
|
// Cache accepts a route's handler which will cache its response and a time.Duration(int64) which is the expiration duration
|
||||||
Cache(HandlerFunc, time.Duration) HandlerFunc
|
Cache(HandlerFunc, time.Duration) HandlerFunc
|
||||||
// ServeRemoteCache creates & returns a new handler which saves cache by POST method and serves a cache entry by GET method to clients
|
// ServeRemoteCache creates & returns a new handler which saves cache by POST method and serves a cache entry by GET method to clients
|
||||||
// usually set it with iris.Any,
|
// usually set it with iris.Any,
|
||||||
// but developer is able to set different paths for save or get cache entries: using the iris.Post/.Get(...,iris.ServeRemote())
|
// but developer is able to set different paths for save or get cache entries: using the iris.Post/.Get(...,iris.ServeRemote())
|
||||||
//
|
// CacheRemote IS not ready for production yet and that's why it is not in docs or history yet.
|
||||||
// IT IS NOT READY FOR PRODUCTION YET, READ THE HISTORY.md for the available working cache methods
|
// propably this method will go to another package which will be ready at the next 24 hours,
|
||||||
ServeRemoteCache() HandlerFunc
|
// because it can work both on iris and raw net/http, lets no limit it:)
|
||||||
|
ServeRemoteCache(time.Duration) HandlerFunc
|
||||||
// Invalidate accepts a cache key (which can be retrieved by 'GetCacheKey') and remove its cache response body
|
// Invalidate accepts a cache key (which can be retrieved by 'GetCacheKey') and remove its cache response body
|
||||||
InvalidateCache(string)
|
InvalidateCache(string)
|
||||||
}
|
}
|
||||||
|
@ -30,52 +28,57 @@ type (
|
||||||
cache map[string]*cacheEntry
|
cache map[string]*cacheEntry
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
// keep track of the minimum cache duration of all cache entries, this will be used when gcDuration inside .start() is <=time.Second
|
// keep track of the minimum cache duration of all cache entries, this will be used when gcDuration inside .start() is <=time.Second
|
||||||
lowerExpiration time.Duration
|
gcDuration time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheEntry struct {
|
cacheEntry struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
contentType string
|
contentType string
|
||||||
value []byte
|
body []byte
|
||||||
expires time.Time
|
// we could have a new Timer foreach cache entry in order to be persise on the expiration but this will cost us a lot of performance,
|
||||||
|
// (the ticker should be stopped if delete or key ovveride and so on...)
|
||||||
|
// but I chosen to just have a generic timer with its tick on the lowest 'expires' of all cache entries that cache keeps
|
||||||
|
expires time.Time
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ CacheService = &cacheService{}
|
func (e *cacheEntry) serve(ctx *Context) {
|
||||||
|
ctx.SetContentType(e.contentType)
|
||||||
|
ctx.SetStatusCode(e.statusCode)
|
||||||
|
ctx.RequestCtx.Write(e.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ CacheServiceAPI = &cacheService{}
|
||||||
|
|
||||||
func newCacheService() *cacheService {
|
func newCacheService() *cacheService {
|
||||||
cs := &cacheService{
|
cs := &cacheService{
|
||||||
cache: make(map[string]*cacheEntry),
|
cache: make(map[string]*cacheEntry),
|
||||||
mu: sync.RWMutex{},
|
mu: sync.RWMutex{},
|
||||||
lowerExpiration: time.Second,
|
gcDuration: -1, // will set as the lowest of the cache entries, if not set then the cache doesn't starts its garbage collector
|
||||||
}
|
}
|
||||||
|
|
||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start is not called via newCacheService because
|
// start called last (after the lowest cache gc duration has been setted by the Cache funcs)
|
||||||
// if gcDuration is <=time.Second
|
func (cs *cacheService) start() {
|
||||||
// then start should check and set the gcDuration from the TOTAL CACHE ENTRIES lowest expiration duration
|
if cs.gcDuration > 0 {
|
||||||
func (cs *cacheService) Start(gcDuration time.Duration) {
|
// start the timer to check for expirated cache entries
|
||||||
|
tick := time.Tick(cs.gcDuration)
|
||||||
if gcDuration <= minimumAllowedCacheDuration {
|
go func() {
|
||||||
gcDuration = cs.lowerExpiration
|
for range tick {
|
||||||
}
|
cs.mu.Lock()
|
||||||
|
now := time.Now()
|
||||||
// start the timer to check for expirated cache entries
|
for k, v := range cs.cache {
|
||||||
tick := time.Tick(gcDuration)
|
if now.After(v.expires) {
|
||||||
go func() {
|
println("remove cache")
|
||||||
for range tick {
|
delete(cs.cache, k)
|
||||||
cs.mu.Lock()
|
}
|
||||||
now := time.Now()
|
|
||||||
for k, v := range cs.cache {
|
|
||||||
if now.Before(v.expires) {
|
|
||||||
delete(cs.cache, k)
|
|
||||||
}
|
}
|
||||||
|
cs.mu.Unlock()
|
||||||
}
|
}
|
||||||
cs.mu.Unlock()
|
}()
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,28 +92,37 @@ func (cs *cacheService) get(key string) *cacheEntry {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't set it to zero value, just 2050 year is enough xD
|
var minimumAllowedCacheDuration = 2 * time.Second
|
||||||
var expiresNever = time.Date(2050, time.January, 10, 23, 0, 0, 0, time.UTC)
|
|
||||||
var minimumAllowedCacheDuration = time.Second
|
|
||||||
|
|
||||||
func (cs *cacheService) set(key string, statusCode int, contentType string, value []byte, expiration time.Duration) {
|
func validateCacheDuration(expiration time.Duration) time.Duration {
|
||||||
if statusCode == 0 {
|
if expiration <= minimumAllowedCacheDuration {
|
||||||
|
expiration = minimumAllowedCacheDuration * 2
|
||||||
|
}
|
||||||
|
return expiration
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateStatusCode(statusCode int) int {
|
||||||
|
if statusCode <= 0 {
|
||||||
statusCode = StatusOK
|
statusCode = StatusOK
|
||||||
}
|
}
|
||||||
if contentType == "" {
|
return statusCode
|
||||||
contentType = contentText
|
}
|
||||||
|
|
||||||
|
func validateContentType(cType string) string {
|
||||||
|
if cType == "" {
|
||||||
|
cType = contentText
|
||||||
}
|
}
|
||||||
|
return cType
|
||||||
|
}
|
||||||
|
|
||||||
entry := &cacheEntry{contentType: contentType, statusCode: statusCode, value: value}
|
func (cs *cacheService) set(key string, statusCode int, cType string, body []byte, expiration time.Duration) {
|
||||||
|
entry := &cacheEntry{
|
||||||
if expiration <= minimumAllowedCacheDuration {
|
statusCode: validateStatusCode(statusCode),
|
||||||
// Cache function tries to set the expiration(seconds) from header "cache-control" if expiration <=minimumAllowedCacheDuration
|
contentType: validateContentType(cType),
|
||||||
// but if cache-control is missing then set it to 5 minutes
|
expires: time.Now().Add(validateCacheDuration(expiration)),
|
||||||
expiration = 5 * time.Minute
|
body: body,
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.expires = time.Now().Add(expiration)
|
|
||||||
|
|
||||||
cs.mu.Lock()
|
cs.mu.Lock()
|
||||||
cs.cache[key] = entry
|
cs.cache[key] = entry
|
||||||
cs.mu.Unlock()
|
cs.mu.Unlock()
|
||||||
|
@ -136,7 +148,7 @@ func GetCacheKey(ctx *Context) string {
|
||||||
//
|
//
|
||||||
// Example: https://github.com/iris-contrib/examples/tree/master/cache_body
|
// Example: https://github.com/iris-contrib/examples/tree/master/cache_body
|
||||||
func InvalidateCache(key string) {
|
func InvalidateCache(key string) {
|
||||||
Default.CacheService.InvalidateCache(key)
|
Default.InvalidateCache(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvalidateCache clears the cache body for a specific key(request uri, can be retrieved by GetCacheKey(ctx))
|
// InvalidateCache clears the cache body for a specific key(request uri, can be retrieved by GetCacheKey(ctx))
|
||||||
|
@ -162,7 +174,15 @@ func (cs *cacheService) InvalidateCache(key string) {
|
||||||
//
|
//
|
||||||
// Example: https://github.com/iris-contrib/examples/tree/master/cache_body
|
// Example: https://github.com/iris-contrib/examples/tree/master/cache_body
|
||||||
func Cache(bodyHandler HandlerFunc, expiration time.Duration) HandlerFunc {
|
func Cache(bodyHandler HandlerFunc, expiration time.Duration) HandlerFunc {
|
||||||
return Default.CacheService.Cache(bodyHandler, expiration)
|
return Default.Cache(bodyHandler, expiration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResponseContentType(ctx *Context) string {
|
||||||
|
return validateContentType(string(ctx.Response.Header.ContentType()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResponseStatusCode(ctx *Context) int {
|
||||||
|
return validateStatusCode(ctx.Response.StatusCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache is just a wrapper for a route's handler which you want to enable body caching
|
// Cache is just a wrapper for a route's handler which you want to enable body caching
|
||||||
|
@ -177,34 +197,37 @@ func Cache(bodyHandler HandlerFunc, expiration time.Duration) HandlerFunc {
|
||||||
//
|
//
|
||||||
// Example: https://github.com/iris-contrib/examples/tree/master/cache_body
|
// Example: https://github.com/iris-contrib/examples/tree/master/cache_body
|
||||||
func (cs *cacheService) Cache(bodyHandler HandlerFunc, expiration time.Duration) HandlerFunc {
|
func (cs *cacheService) Cache(bodyHandler HandlerFunc, expiration time.Duration) HandlerFunc {
|
||||||
|
expiration = validateCacheDuration(expiration)
|
||||||
|
|
||||||
// the first time the lowerExpiration should be > time.Second, so:
|
if cs.gcDuration == -1 {
|
||||||
if cs.lowerExpiration == time.Second {
|
// if gc duration is not setted yet or this is the only one Cache which happens to have bigger expiration than the minimumAllowedCacheDuration
|
||||||
cs.lowerExpiration = expiration
|
// then set that as the gcDuration
|
||||||
} else if expiration > time.Second && expiration < cs.lowerExpiration {
|
cs.gcDuration = expiration // the first time the lowerExpiration should be > minimumAllowedCacheDuration so:
|
||||||
cs.lowerExpiration = expiration
|
} else if expiration < cs.gcDuration { // find the lower
|
||||||
|
// if this expiration is lower than the already setted, set the gcDuration to this
|
||||||
|
cs.gcDuration = expiration
|
||||||
}
|
}
|
||||||
|
|
||||||
h := func(ctx *Context) {
|
h := func(ctx *Context) {
|
||||||
key := GetCacheKey(ctx)
|
key := GetCacheKey(ctx)
|
||||||
if v := cs.get(key); v != nil {
|
if v := cs.get(key); v != nil {
|
||||||
ctx.SetContentType(v.contentType)
|
v.serve(ctx)
|
||||||
ctx.SetStatusCode(v.statusCode)
|
|
||||||
ctx.RequestCtx.Write(v.value)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if not found then serve this:
|
// if not found then serve the handler and collect its results after
|
||||||
bodyHandler.Serve(ctx)
|
bodyHandler.Serve(ctx)
|
||||||
|
|
||||||
if expiration <= minimumAllowedCacheDuration {
|
if expiration <= minimumAllowedCacheDuration {
|
||||||
// try to set the expiraion from header
|
// try to set the expiraion from header
|
||||||
expiration = time.Duration(ctx.MaxAge()) * time.Second
|
expiration = time.Duration(ctx.MaxAge()) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
cType := string(ctx.Response.Header.Peek(contentType))
|
cType := getResponseContentType(ctx)
|
||||||
statusCode := ctx.RequestCtx.Response.StatusCode()
|
statusCode := getResponseStatusCode(ctx)
|
||||||
|
body := ctx.Response.Body()
|
||||||
// and set the cache value as its response body in a goroutine, because we want to exit from the route's handler as soon as possible
|
// and set the cache value as its response body in a goroutine, because we want to exit from the route's handler as soon as possible
|
||||||
go cs.set(key, statusCode, cType, ctx.Response.Body(), expiration)
|
go cs.set(key, statusCode, cType, body, expiration)
|
||||||
}
|
}
|
||||||
|
|
||||||
return h
|
return h
|
||||||
|
@ -217,6 +240,13 @@ func GetRemoteCacheKey(ctx *Context) string {
|
||||||
return url.QueryEscape(ctx.Request.URI().String())
|
return url.QueryEscape(ctx.Request.URI().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
queryCacheKey = "cache_key"
|
||||||
|
queryCacheDuration = "cache_duration"
|
||||||
|
queryCacheStatusCode = "cache_status_code"
|
||||||
|
queryCacheContentType = "cache_content_type"
|
||||||
|
)
|
||||||
|
|
||||||
// RemoteCache accepts the remote server address and path of the external cache service, the body handler and optional an expiration
|
// RemoteCache accepts the remote server address and path of the external cache service, the body handler and optional an expiration
|
||||||
// the last 2 receivers works like .Cache(...) function
|
// the last 2 receivers works like .Cache(...) function
|
||||||
//
|
//
|
||||||
|
@ -227,7 +257,7 @@ func GetRemoteCacheKey(ctx *Context) string {
|
||||||
func RemoteCache(cacheServerAddr string, bodyHandler HandlerFunc, expiration time.Duration) HandlerFunc {
|
func RemoteCache(cacheServerAddr string, bodyHandler HandlerFunc, expiration time.Duration) HandlerFunc {
|
||||||
client := fasthttp.Client{}
|
client := fasthttp.Client{}
|
||||||
// buf := utils.NewBufferPool(10)
|
// buf := utils.NewBufferPool(10)
|
||||||
cacheDurationStr := fmt.Sprintf("%f", expiration.Seconds())
|
cacheDurationStr := strconv.Itoa(int(expiration.Seconds()))
|
||||||
h := func(ctx *Context) {
|
h := func(ctx *Context) {
|
||||||
req := fasthttp.AcquireRequest()
|
req := fasthttp.AcquireRequest()
|
||||||
req.SetRequestURI(cacheServerAddr)
|
req.SetRequestURI(cacheServerAddr)
|
||||||
|
@ -247,12 +277,10 @@ func RemoteCache(cacheServerAddr string, bodyHandler HandlerFunc, expiration tim
|
||||||
req.URI().QueryArgs().Add("cache_status_code", statusCode)
|
req.URI().QueryArgs().Add("cache_status_code", statusCode)
|
||||||
cType := string(ctx.Response.Header.Peek(contentType))
|
cType := string(ctx.Response.Header.Peek(contentType))
|
||||||
req.URI().QueryArgs().Add("cache_content_type", cType)
|
req.URI().QueryArgs().Add("cache_content_type", cType)
|
||||||
postArgs := fasthttp.AcquireArgs()
|
|
||||||
postArgs.SetBytesV("cache_body", ctx.Response.Body())
|
|
||||||
|
|
||||||
|
req.SetBody(ctx.Response.Body())
|
||||||
go func() {
|
go func() {
|
||||||
client.DoTimeout(req, res, time.Duration(5)*time.Second)
|
client.DoTimeout(req, res, time.Duration(5)*time.Second)
|
||||||
fasthttp.ReleaseArgs(postArgs)
|
|
||||||
fasthttp.ReleaseRequest(req)
|
fasthttp.ReleaseRequest(req)
|
||||||
fasthttp.ReleaseResponse(res)
|
fasthttp.ReleaseResponse(res)
|
||||||
}()
|
}()
|
||||||
|
@ -285,11 +313,9 @@ func RemoteCache(cacheServerAddr string, bodyHandler HandlerFunc, expiration tim
|
||||||
//
|
//
|
||||||
// Note that it depends on a station instance's cache service.
|
// Note that it depends on a station instance's cache service.
|
||||||
// Do not try to call it from default' station if you use the form of app := iris.New(),
|
// Do not try to call it from default' station if you use the form of app := iris.New(),
|
||||||
// use the app.ServeRemoteCache instead of iris.Cache
|
// use the app.ServeRemoteCache instead of iris.ServeRemoteCache
|
||||||
//
|
func ServeRemoteCache(gcDuration time.Duration) HandlerFunc {
|
||||||
// IT IS NOT READY FOR PRODUCTION YET, READ THE HISTORY.md for the available working cache methods
|
return Default.ServeRemoteCache(gcDuration)
|
||||||
func ServeRemoteCache() HandlerFunc {
|
|
||||||
return Default.CacheService.ServeRemoteCache()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeRemoteCache usage: iris.Any("/cacheservice", iris.ServeRemoteCache())
|
// ServeRemoteCache usage: iris.Any("/cacheservice", iris.ServeRemoteCache())
|
||||||
|
@ -303,10 +329,10 @@ func ServeRemoteCache() HandlerFunc {
|
||||||
//
|
//
|
||||||
// Note that it depends on a station instance's cache service.
|
// Note that it depends on a station instance's cache service.
|
||||||
// Do not try to call it from default' station if you use the form of app := iris.New(),
|
// Do not try to call it from default' station if you use the form of app := iris.New(),
|
||||||
// use the app.ServeRemoteCache instead of iris.Cache
|
// use the app.ServeRemoteCache instead of iris.ServeRemoteCache
|
||||||
//
|
func (cs *cacheService) ServeRemoteCache(gcDuration time.Duration) HandlerFunc {
|
||||||
// IT IS NOT READY FOR PRODUCTION YET, READ THE HISTORY.md for the available working cache methods
|
cs.gcDuration = validateCacheDuration(gcDuration)
|
||||||
func (cs *cacheService) ServeRemoteCache() HandlerFunc {
|
|
||||||
h := func(ctx *Context) {
|
h := func(ctx *Context) {
|
||||||
key := ctx.URLParam("cache_key")
|
key := ctx.URLParam("cache_key")
|
||||||
if key == "" {
|
if key == "" {
|
||||||
|
@ -316,22 +342,18 @@ func (cs *cacheService) ServeRemoteCache() HandlerFunc {
|
||||||
|
|
||||||
if ctx.IsGet() {
|
if ctx.IsGet() {
|
||||||
if v := cs.get(key); v != nil {
|
if v := cs.get(key); v != nil {
|
||||||
ctx.SetStatusCode(v.statusCode)
|
v.serve(ctx)
|
||||||
ctx.SetContentType(v.contentType)
|
|
||||||
ctx.RequestCtx.Write(v.value)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if ctx.IsPost() {
|
} else if ctx.IsPost() {
|
||||||
// get the cache expiration via url param
|
// get the cache expiration via url param
|
||||||
expirationSeconds, err := ctx.URLParamInt64("cache_duration")
|
expirationSeconds, err := ctx.URLParamInt64("cache_duration")
|
||||||
// get the body from the post arguments or requested body
|
// get the body from the requested body
|
||||||
body := ctx.PostArgs().Peek("cache_body")
|
body := ctx.Request.Body()
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
body = ctx.Request.Body()
|
ctx.SetStatusCode(StatusBadRequest)
|
||||||
if len(body) == 0 {
|
return
|
||||||
ctx.SetStatusCode(StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// get the expiration from the "cache-control's maxage" if no url param is setted
|
// get the expiration from the "cache-control's maxage" if no url param is setted
|
||||||
if expirationSeconds <= 0 || err != nil {
|
if expirationSeconds <= 0 || err != nil {
|
||||||
|
@ -340,7 +362,7 @@ func (cs *cacheService) ServeRemoteCache() HandlerFunc {
|
||||||
|
|
||||||
// if not setted then try to get it via
|
// if not setted then try to get it via
|
||||||
if expirationSeconds <= 0 {
|
if expirationSeconds <= 0 {
|
||||||
expirationSeconds = 5 * 60 // 5 minutes
|
expirationSeconds = int64(minimumAllowedCacheDuration.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheDuration := time.Duration(expirationSeconds) * time.Second
|
cacheDuration := time.Duration(expirationSeconds) * time.Second
|
||||||
|
@ -366,5 +388,5 @@ func (cs *cacheService) ServeRemoteCache() HandlerFunc {
|
||||||
ctx.SetStatusCode(StatusBadRequest)
|
ctx.SetStatusCode(StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cs.Cache(h, -1)
|
return h
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ All features of Sundown are supported, including:
|
||||||
// EXAMPLE: https://github.com/iris-contrib/examples/tree/master/cache_body
|
// EXAMPLE: https://github.com/iris-contrib/examples/tree/master/cache_body
|
||||||
func TestCacheCanRender(t *testing.T) {
|
func TestCacheCanRender(t *testing.T) {
|
||||||
iris.ResetDefault()
|
iris.ResetDefault()
|
||||||
iris.Config.CacheGCDuration = time.Duration(10) * time.Second
|
|
||||||
iris.Config.IsDevelopment = true
|
iris.Config.IsDevelopment = true
|
||||||
defer iris.Close()
|
defer iris.Close()
|
||||||
var i = 1
|
var i = 1
|
||||||
|
@ -83,48 +83,45 @@ func TestCacheCanRender(t *testing.T) {
|
||||||
time.Sleep(5 * time.Second) // let's sleep for a while in order to be saved in cache(running in goroutine)
|
time.Sleep(5 * time.Second) // let's sleep for a while in order to be saved in cache(running in goroutine)
|
||||||
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody) // the 1 minute didnt' passed so it should work
|
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody) // the 1 minute didnt' passed so it should work
|
||||||
|
|
||||||
// travis... and time sleep not a good idea for testing, we will see what we can do other day, the cache is tested on examples too*
|
|
||||||
/*e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody) // the cache still son the corrrect body so no StatusNoContent fires
|
|
||||||
time.Sleep(time.Duration(5) * time.Second) // 4 depends on the CacheGCDuration not the expiration
|
|
||||||
|
|
||||||
// the cache should be cleared and now i = 2 then it should run the iris.StatusNoContent with empty body ( we don't use the EmitError)
|
|
||||||
e.GET("/").Expect().Status(iris.StatusNoContent).Body().Empty()
|
|
||||||
time.Sleep(time.Duration(5) * time.Second)
|
|
||||||
|
|
||||||
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
|
|
||||||
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CacheRemote IS not ready for production yet
|
||||||
// func TestCacheRemote(t *testing.T) {
|
// func TestCacheRemote(t *testing.T) {
|
||||||
// iris.ResetDefault()
|
// iris.ResetDefault()
|
||||||
// // setup the remote cache service listening on localhost:8888/cache
|
// // setup the remote cache service listening on localhost:8888/cache
|
||||||
// remoteService := iris.New(iris.OptionCacheGCDuration(1*time.Minute), iris.OptionDisableBanner(true), iris.OptionIsDevelopment(true))
|
// remoteService := iris.New(iris.OptionDisableBanner(true))
|
||||||
// remoteService.Any("/cache", remoteService.ServeRemoteCache())
|
// remoteService.Any("/cache", remoteService.ServeRemoteCache(5*time.Second)) // clear the gc every 5 seconds
|
||||||
// defer remoteService.Close()
|
// defer remoteService.Close()
|
||||||
// go remoteService.Listen(":8888")
|
// go remoteService.Listen("localhost:8888")
|
||||||
// <-remoteService.Available
|
// <-remoteService.Available
|
||||||
//
|
//
|
||||||
// app := iris.New(iris.OptionIsDevelopment(true))
|
// app := iris.New()
|
||||||
//
|
//
|
||||||
// expectedBody := iris.SerializeToString("text/markdown", testMarkdownContents)
|
// n := 1
|
||||||
//
|
|
||||||
// i := 1
|
|
||||||
// bodyHandler := func(ctx *iris.Context) {
|
// bodyHandler := func(ctx *iris.Context) {
|
||||||
// if i%2 == 0 { // only for testing
|
// n++
|
||||||
// ctx.SetStatusCode(iris.StatusNoContent)
|
|
||||||
// i++
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// i++
|
|
||||||
// ctx.Markdown(iris.StatusOK, testMarkdownContents)
|
// ctx.Markdown(iris.StatusOK, testMarkdownContents)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// app.Get("/", iris.RemoteCache("http://127.0.0.1:8888/cache", bodyHandler, time.Duration(15)*time.Second))
|
// app.Get("/", iris.RemoteCache("http://localhost:8888/cache", bodyHandler, 10*time.Second))
|
||||||
//
|
//
|
||||||
// e := httptest.New(app, t, httptest.Debug(true))
|
// e := httptest.New(app, t, httptest.Debug(false))
|
||||||
|
//
|
||||||
|
// expectedBody := app.SerializeToString("text/markdown", testMarkdownContents)
|
||||||
//
|
//
|
||||||
// e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
|
// e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
|
||||||
|
// time.Sleep(3 * time.Second) // let's wait a while because saving is going on a goroutine (in some ms, but travis is slow so 2 seconds wait)
|
||||||
|
// // we are in cache, so the 'n' should be 1
|
||||||
|
// e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
|
||||||
|
// if n > 1 {
|
||||||
|
// // n should be 1 because it doesn't changed after the first call
|
||||||
|
// t.Fatalf("Expected n = %d but got %d. Cache has problems!!", 1, n)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // let's wait 5 more seconds, the cache should be cleared now the n should be 2
|
||||||
// time.Sleep(5 * time.Second)
|
// time.Sleep(5 * time.Second)
|
||||||
// e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedBody)
|
// e.GET("/").Expect().Status(iris.StatusNoContent).Body().Empty()
|
||||||
//
|
// if n != 2 {
|
||||||
|
// t.Fatalf("Expected n = %d but got %d. Cache has problems!!", 2, n)
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
|
@ -204,15 +204,6 @@ type Configuration struct {
|
||||||
// Sessions contains the configs for sessions
|
// Sessions contains the configs for sessions
|
||||||
Sessions SessionsConfiguration
|
Sessions SessionsConfiguration
|
||||||
|
|
||||||
// CacheGCDuration the cache gc duration,
|
|
||||||
// when this duration is passed then the cache is checking for each of the cache entries' expiration field
|
|
||||||
// this clears only the Cached handlers, so if you don't want cache then don't pass your handler arround the Cache wrapper
|
|
||||||
// it's like the session's GcDuration field
|
|
||||||
//
|
|
||||||
// Is if your app is big and not very changable (like a blog) set this duration big , like 5 hours
|
|
||||||
//
|
|
||||||
// Defaults to Auto, Auto means that is setted by the lowest expiration of all cach entries
|
|
||||||
CacheGCDuration time.Duration
|
|
||||||
// Websocket contains the configs for Websocket's server integration
|
// Websocket contains the configs for Websocket's server integration
|
||||||
Websocket WebsocketConfiguration
|
Websocket WebsocketConfiguration
|
||||||
|
|
||||||
|
@ -434,20 +425,6 @@ var (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionCacheGCDuration ses the cache gc duration,
|
|
||||||
// when this duration is passed then the cache is checking for each of the cache entries' expiration field
|
|
||||||
// this clears only the Cached handlers, so if you don't want cache then don't pass your handler arround the Cache wrapper
|
|
||||||
// it's like the session's GcDuration field
|
|
||||||
//
|
|
||||||
// Is if your app is big and not very changable (like a blog) set this duration big , like 5 hours
|
|
||||||
//
|
|
||||||
// Defaults to Auto, Auto means that is setted by the lowest expiration of all cach entries
|
|
||||||
OptionCacheGCDuration = func(val time.Duration) OptionSet {
|
|
||||||
return func(c *Configuration) {
|
|
||||||
c.CacheGCDuration = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptionIsDevelopment iris will act like a developer, for example
|
// OptionIsDevelopment iris will act like a developer, for example
|
||||||
// If true then re-builds the templates on each request
|
// If true then re-builds the templates on each request
|
||||||
// Default is false
|
// Default is false
|
||||||
|
@ -565,7 +542,6 @@ func DefaultConfiguration() Configuration {
|
||||||
Charset: DefaultCharset,
|
Charset: DefaultCharset,
|
||||||
Gzip: false,
|
Gzip: false,
|
||||||
Sessions: DefaultSessionsConfiguration(),
|
Sessions: DefaultSessionsConfiguration(),
|
||||||
CacheGCDuration: minimumAllowedCacheDuration,
|
|
||||||
Websocket: DefaultWebsocketConfiguration(),
|
Websocket: DefaultWebsocketConfiguration(),
|
||||||
Other: options.Options{},
|
Other: options.Options{},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1133,7 +1133,7 @@ func (ctx *Context) MaxAge() int64 {
|
||||||
|
|
||||||
// InvalidateCache clears the cache manually for this request uri context's handler's route
|
// InvalidateCache clears the cache manually for this request uri context's handler's route
|
||||||
func (ctx *Context) InvalidateCache() {
|
func (ctx *Context) InvalidateCache() {
|
||||||
ctx.framework.CacheService.InvalidateCache(GetCacheKey(ctx))
|
ctx.framework.cacheService.InvalidateCache(GetCacheKey(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log logs to the iris defined logger
|
// Log logs to the iris defined logger
|
||||||
|
|
10
iris.go
10
iris.go
|
@ -142,7 +142,7 @@ type (
|
||||||
// FrameworkAPI contains the main Iris Public API
|
// FrameworkAPI contains the main Iris Public API
|
||||||
FrameworkAPI interface {
|
FrameworkAPI interface {
|
||||||
MuxAPI
|
MuxAPI
|
||||||
CacheService
|
CacheServiceAPI
|
||||||
Set(...OptionSetter)
|
Set(...OptionSetter)
|
||||||
Must(error)
|
Must(error)
|
||||||
Build()
|
Build()
|
||||||
|
@ -176,7 +176,7 @@ type (
|
||||||
// Implements the FrameworkAPI
|
// Implements the FrameworkAPI
|
||||||
Framework struct {
|
Framework struct {
|
||||||
*muxAPI
|
*muxAPI
|
||||||
CacheService
|
*cacheService
|
||||||
// HTTP Server runtime fields is the iris' defined main server, developer can use unlimited number of servers
|
// HTTP Server runtime fields is the iris' defined main server, developer can use unlimited number of servers
|
||||||
// note: they're available after .Build, and .Serve/Listen/ListenTLS/ListenLETSENCRYPT/ListenUNIX
|
// note: they're available after .Build, and .Serve/Listen/ListenTLS/ListenLETSENCRYPT/ListenUNIX
|
||||||
ln net.Listener
|
ln net.Listener
|
||||||
|
@ -233,7 +233,7 @@ func New(setters ...OptionSetter) *Framework {
|
||||||
"urlpath": s.Path,
|
"urlpath": s.Path,
|
||||||
})
|
})
|
||||||
// set the cache service
|
// set the cache service
|
||||||
s.CacheService = newCacheService()
|
s.cacheService = newCacheService()
|
||||||
}
|
}
|
||||||
|
|
||||||
// websocket & sessions
|
// websocket & sessions
|
||||||
|
@ -354,7 +354,9 @@ func (s *Framework) Build() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the cache gc duration and start service
|
// set the cache gc duration and start service
|
||||||
s.CacheService.Start(s.Config.CacheGCDuration)
|
if s.cacheService.gcDuration > 0 {
|
||||||
|
s.cacheService.start()
|
||||||
|
}
|
||||||
|
|
||||||
if s.Config.Websocket.Endpoint != "" {
|
if s.Config.Websocket.Endpoint != "" {
|
||||||
// register the websocket server and listen to websocket connections when/if $instance.Websocket.OnConnection called by the dev
|
// register the websocket server and listen to websocket connections when/if $instance.Websocket.OnConnection called by the dev
|
||||||
|
|
Loading…
Reference in New Issue
Block a user