mirror of
https://github.com/kataras/iris.git
synced 2026-01-04 18:57:03 +00:00
Update to 7.2.0. Read https://github.com/kataras/iris/blob/master/HISTORY.md#th-15-june-2017--v720
Fix https://github.com/iris-contrib/community-board/issues/12 . Read more: https://github.com/kataras/iris/blob/master/HISTORY.md#th-15-june-2017--v720 Former-commit-id: 398cb69fdc7e5367b147693371287ffb7b912feb
This commit is contained in:
25
cache/cache.go
vendored
25
cache/cache.go
vendored
@@ -17,7 +17,7 @@ Example code:
|
||||
|
||||
func main(){
|
||||
app := iris.Default()
|
||||
cachedHandler := cache.CacheHandler(h, 2 *time.Minute)
|
||||
cachedHandler := cache.WrapHandler(h, 2 *time.Minute)
|
||||
app.Get("/hello", cachedHandler)
|
||||
app.Run(iris.Addr(":8080"))
|
||||
}
|
||||
@@ -44,12 +44,12 @@ import (
|
||||
//
|
||||
// All type of responses are cached, templates, json, text, anything.
|
||||
//
|
||||
// You can add validators with this function
|
||||
// You can add validators with this function.
|
||||
func Cache(bodyHandler context.Handler, expiration time.Duration) *client.Handler {
|
||||
return client.NewHandler(bodyHandler, expiration)
|
||||
}
|
||||
|
||||
// CacheHandler accepts two parameters
|
||||
// WrapHandler accepts two parameters
|
||||
// first is the context.Handler which you want to cache its result
|
||||
// the second is, optional, the cache Entry's expiration duration
|
||||
// if the expiration <=2 seconds then expiration is taken by the "cache-control's maxage" header
|
||||
@@ -57,11 +57,26 @@ func Cache(bodyHandler context.Handler, expiration time.Duration) *client.Handle
|
||||
//
|
||||
// All type of responses are cached, templates, json, text, anything.
|
||||
//
|
||||
// it returns the context.Handler, for more options use the .Cache .
|
||||
func CacheHandler(bodyHandler context.Handler, expiration time.Duration) context.Handler {
|
||||
// it returns a context.Handler, for more options use the .Cache .
|
||||
func WrapHandler(bodyHandler context.Handler, expiration time.Duration) context.Handler {
|
||||
return Cache(bodyHandler, expiration).ServeHTTP
|
||||
}
|
||||
|
||||
// Handler accepts one single parameter:
|
||||
// the cache Entry's expiration duration
|
||||
// if the expiration <=2 seconds then expiration is taken by the "cache-control's maxage" header
|
||||
// returns context.Handler.
|
||||
//
|
||||
// It's the same as Cache and WrapHandler but it sets the "bodyHandler" to the next handler in the chain.
|
||||
//
|
||||
// All type of responses are cached, templates, json, text, anything.
|
||||
//
|
||||
// it returns a context.Handler, for more options use the .Cache .
|
||||
func Handler(expiration time.Duration) context.Handler {
|
||||
h := WrapHandler(nil, expiration)
|
||||
return h
|
||||
}
|
||||
|
||||
var (
|
||||
// NoCache called when a particular handler is not valid for cache.
|
||||
// If this function called inside a handler then the handler is not cached
|
||||
|
||||
197
cache/cache_test.go
vendored
Normal file
197
cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
// black-box testing
|
||||
package cache_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/cache"
|
||||
"github.com/kataras/iris/cache/client/rule"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
|
||||
"github.com/iris-contrib/httpexpect"
|
||||
"github.com/kataras/iris/httptest"
|
||||
)
|
||||
|
||||
var (
|
||||
cacheDuration = 2 * time.Second
|
||||
expectedBodyStr = "Imagine it as a big message to achieve x20 response performance!"
|
||||
errTestFailed = errors.New("expected the main handler to be executed %d times instead of %d")
|
||||
)
|
||||
|
||||
func runTest(e *httpexpect.Expect, counterPtr *uint32, expectedBodyStr string, nocache string) error {
|
||||
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||
time.Sleep(cacheDuration / 5) // lets wait for a while, cache should be saved and ready
|
||||
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||
counter := atomic.LoadUint32(counterPtr)
|
||||
if counter > 1 {
|
||||
// n should be 1 because it doesn't changed after the first call
|
||||
return errTestFailed.Format(1, counter)
|
||||
}
|
||||
time.Sleep(cacheDuration)
|
||||
|
||||
// cache should be cleared now
|
||||
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||
time.Sleep(cacheDuration / 5)
|
||||
// let's call again , the cache should be saved
|
||||
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||
counter = atomic.LoadUint32(counterPtr)
|
||||
if counter != 2 {
|
||||
return errTestFailed.Format(2, counter)
|
||||
}
|
||||
|
||||
// we have cache response saved for the "/" path, we have some time more here, but here
|
||||
// we will make the requestS with some of the deniers options
|
||||
e.GET("/").WithHeader("max-age", "0").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||
e.GET("/").WithHeader("Authorization", "basic or anything").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||
counter = atomic.LoadUint32(counterPtr)
|
||||
if counter != 4 {
|
||||
return errTestFailed.Format(4, counter)
|
||||
}
|
||||
|
||||
if nocache != "" {
|
||||
// test the NoCache, first sleep to pass the cache expiration,
|
||||
// second add to the cache with a valid request and response
|
||||
// third, do it with the "/nocache" path (static for now, pure test design) given by the consumer
|
||||
time.Sleep(cacheDuration)
|
||||
|
||||
// cache should be cleared now, this should work because we are not in the "nocache" path
|
||||
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter = 5
|
||||
time.Sleep(cacheDuration / 5)
|
||||
|
||||
// let's call the "nocache", the expiration is not passed so but the "nocache"
|
||||
// route's path has the cache.NoCache so it should be not cached and the counter should be ++
|
||||
e.GET(nocache).Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter should be 6
|
||||
counter = atomic.LoadUint32(counterPtr)
|
||||
if counter != 6 { // 4 before, 5 with the first call to store the cache, and six with the no cache, again original handler executation
|
||||
return errTestFailed.Format(6, counter)
|
||||
}
|
||||
|
||||
// let's call again the "/", the expiration is not passed so it should be cached
|
||||
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||
counter = atomic.LoadUint32(counterPtr)
|
||||
if counter != 6 {
|
||||
return errTestFailed.Format(6, counter)
|
||||
}
|
||||
|
||||
// but now check for the No
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNoCache(t *testing.T) {
|
||||
app := iris.New()
|
||||
var n uint32
|
||||
|
||||
app.Get("/", cache.WrapHandler(func(ctx context.Context) {
|
||||
atomic.AddUint32(&n, 1)
|
||||
ctx.Write([]byte(expectedBodyStr))
|
||||
}, cacheDuration))
|
||||
|
||||
app.Get("/nocache", cache.WrapHandler(func(ctx context.Context) {
|
||||
cache.NoCache(ctx) // <----
|
||||
atomic.AddUint32(&n, 1)
|
||||
ctx.Write([]byte(expectedBodyStr))
|
||||
}, cacheDuration))
|
||||
|
||||
e := httptest.New(t, app)
|
||||
if err := runTest(e, &n, expectedBodyStr, "/nocache"); err != nil {
|
||||
t.Fatalf(t.Name()+": %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
app := iris.New()
|
||||
var n uint32
|
||||
|
||||
app.Use(cache.Handler(cacheDuration))
|
||||
|
||||
app.Get("/", func(ctx context.Context) {
|
||||
atomic.AddUint32(&n, 1)
|
||||
ctx.Write([]byte(expectedBodyStr))
|
||||
})
|
||||
|
||||
e := httptest.New(t, app)
|
||||
if err := runTest(e, &n, expectedBodyStr, ""); err != nil {
|
||||
t.Fatalf(t.Name()+": %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCacheHandlerParallel(t *testing.T) {
|
||||
t.Parallel()
|
||||
TestCache(t)
|
||||
}
|
||||
|
||||
func TestCacheValidator(t *testing.T) {
|
||||
app := iris.New()
|
||||
var n uint32
|
||||
|
||||
h := func(ctx context.Context) {
|
||||
atomic.AddUint32(&n, 1)
|
||||
ctx.Write([]byte(expectedBodyStr))
|
||||
}
|
||||
|
||||
validCache := cache.Cache(h, cacheDuration)
|
||||
app.Get("/", validCache.ServeHTTP)
|
||||
|
||||
managedCache := cache.Cache(h, cacheDuration)
|
||||
managedCache.AddRule(rule.Validator([]rule.PreValidator{
|
||||
func(ctx context.Context) bool {
|
||||
if ctx.Request().URL.Path == "/invalid" {
|
||||
return false // should always invalid for cache, don't bother to go to try to get or set cache
|
||||
}
|
||||
return true
|
||||
},
|
||||
}, nil))
|
||||
|
||||
managedCache2 := cache.Cache(func(ctx context.Context) {
|
||||
atomic.AddUint32(&n, 1)
|
||||
ctx.Header("DONT", "DO not cache that response even if it was claimed")
|
||||
ctx.Write([]byte(expectedBodyStr))
|
||||
|
||||
}, cacheDuration)
|
||||
managedCache2.AddRule(rule.Validator(nil,
|
||||
[]rule.PostValidator{
|
||||
func(ctx context.Context) bool {
|
||||
if ctx.ResponseWriter().Header().Get("DONT") != "" {
|
||||
return false // it's passed the Claim and now Valid checks if the response contains a header of "DONT"
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
))
|
||||
|
||||
app.Get("/valid", validCache.ServeHTTP)
|
||||
|
||||
app.Get("/invalid", managedCache.ServeHTTP)
|
||||
app.Get("/invalid2", managedCache2.ServeHTTP)
|
||||
|
||||
e := httptest.New(t, app)
|
||||
|
||||
// execute from cache the next time
|
||||
e.GET("/valid").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||
time.Sleep(cacheDuration / 5) // lets wait for a while, cache should be saved and ready
|
||||
e.GET("/valid").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||
counter := atomic.LoadUint32(&n)
|
||||
if counter > 1 {
|
||||
// n should be 1 because it doesn't changed after the first call
|
||||
t.Fatal(errTestFailed.Format(1, counter))
|
||||
}
|
||||
// don't execute from cache, execute the original, counter should ++ here
|
||||
e.GET("/invalid").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter = 2
|
||||
e.GET("/invalid2").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter = 3
|
||||
|
||||
counter = atomic.LoadUint32(&n)
|
||||
if counter != 3 {
|
||||
// n should be 1 because it doesn't changed after the first call
|
||||
t.Fatalf(t.Name()+": %v", errTestFailed.Format(3, counter))
|
||||
}
|
||||
}
|
||||
23
cache/client/handler.go
vendored
23
cache/client/handler.go
vendored
@@ -18,7 +18,8 @@ import (
|
||||
// the validator for each of the incoming requests and post responses
|
||||
type Handler struct {
|
||||
|
||||
// bodyHandler the original route's handler
|
||||
// bodyHandler the original route's handler.
|
||||
// If nil then it tries to take the next handler from the chain.
|
||||
bodyHandler context.Handler
|
||||
|
||||
// Rule optional validators for pre cache and post cache actions
|
||||
@@ -71,8 +72,24 @@ func (h *Handler) AddRule(r rule.Rule) *Handler {
|
||||
func (h *Handler) ServeHTTP(ctx context.Context) {
|
||||
// check for pre-cache validators, if at least one of them return false
|
||||
// for this specific request, then skip the whole cache
|
||||
bodyHandler := h.bodyHandler
|
||||
|
||||
if bodyHandler == nil {
|
||||
if nextHandler := ctx.NextHandler(); nextHandler != nil {
|
||||
// skip prepares the context to move to the next handler if the "nextHandler" has a ctx.Next() inside it,
|
||||
// even if it's not executed because it's cached.
|
||||
ctx.Skip()
|
||||
bodyHandler = nextHandler
|
||||
} else {
|
||||
ctx.StatusCode(500)
|
||||
ctx.WriteString("cache: empty body handler")
|
||||
ctx.StopExecution()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !h.rule.Claim(ctx) {
|
||||
h.bodyHandler(ctx)
|
||||
bodyHandler(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,7 +102,7 @@ func (h *Handler) ServeHTTP(ctx context.Context) {
|
||||
// because the net/http doesn't give us
|
||||
// a built'n way to get the status code & body
|
||||
recorder := ctx.Recorder()
|
||||
h.bodyHandler(ctx)
|
||||
bodyHandler(ctx)
|
||||
|
||||
// now that we have recordered the response,
|
||||
// we are ready to check if that specific response is valid to be stored.
|
||||
|
||||
Reference in New Issue
Block a user