Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make cache package to work across multi handlers. #852

Merged
merged 1 commit into from
Jan 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions _examples/file-server/embedding-files-into-app/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ func (r resource) loadFromBase(dir string) string {
}

result := string(b)
//if runtime.GOOS != "windows" {
// result = strings.Replace(result, "\n", "\r\n", -1)
//}
if runtime.GOOS != "windows" {
result = strings.Replace(result, "\n", "\r\n", -1)
}
return result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"io/ioutil"
"path/filepath"
"runtime"
"strings"
"testing"

Expand Down Expand Up @@ -35,6 +36,7 @@ func (r resource) loadFromBase(dir string) string {
}

result := string(b)

return result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"io/ioutil"
"path/filepath"
"runtime"
"strings"
"testing"

Expand Down Expand Up @@ -34,9 +35,9 @@ func (r resource) loadFromBase(dir string) string {
panic(fullpath + " failed with error: " + err.Error())
}
result := string(b)
// if runtime.GOOS != "windows" {
// result = strings.Replace(result, "\n", "\r\n", -1)
// }
if runtime.GOOS != "windows" {
result = strings.Replace(result, "\n", "\r\n", -1)
}
return result
}

Expand Down
105 changes: 105 additions & 0 deletions _examples/mvc/cache/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
If you want to use it as middleware for the entire controller
you can use its router which is just a sub router to add it as you normally do with standard API:

I'll show you 4 different methods for adding a middleware into an mvc application,
all of those 4 do exactly the same thing, select what you prefer,
I prefer the last code-snippet when I need the middleware to be registered somewhere
else as well, otherwise I am going with the first one:

```go
// 1
mvc.Configure(app.Party("/user"), func(m *mvc.Application) {
m.Router.Use(cache.Handler(10*time.Second))
})
```

```go
// 2
// same:
userRouter := app.Party("/user")
userRouter.Use(cache.Handler(10*time.Second))
mvc.Configure(userRouter, ...)
```

```go
// 3
// same:
userRouter := app.Party("/user", cache.Handler(10*time.Second))
mvc.Configure(userRouter, ...)
```

```go
// 4
// same:
app.PartyFunc("/user", func(r iris.Party){
r.Use(cache.Handler(10*time.Second))
mvc.Configure(r, ...)
})
```

If you want to use a middleware for a single route,
for a single controller's method that is already registered by the engine
and not by custom `Handle` (which you can add
the middleware there on the last parameter) and it's not depend on the `Next Handler` to do its job
then you just call it on the method:

```go
var myMiddleware := myMiddleware.New(...) // this should return an iris/context.Handler

type UserController struct{}
func (c *UserController) GetSomething(ctx iris.Context) {
// ctx.Proceed checks if myMiddleware called `ctx.Next()`
// inside it and returns true if so, otherwise false.
nextCalled := ctx.Proceed(myMiddleware)
if !nextCalled {
return
}

// else do the job here, it's allowed
}
```

And last, if you want to add a middleware on a specific method
and it depends on the next and the whole chain then you have to do it
using the `AfterActivation` like the example below:
*/
package main

import (
"time"

"github.com/kataras/iris"
"github.com/kataras/iris/cache"
"github.com/kataras/iris/mvc"
)

var cacheHandler = cache.Handler(10 * time.Second)

func main() {
app := iris.New()
// You don't have to use .Configure if you do it all in the main func
// mvc.Configure and mvc.New(...).Configure() are just helpers to split
// your code better, here we use the simplest form:
m := mvc.New(app)
m.Handle(&exampleController{})

app.Run(iris.Addr(":8080"))
}

type exampleController struct{}

func (c *exampleController) AfterActivation(a mvc.AfterActivation) {
// select the route based on the method name you want to
// modify.
index := a.GetRoute("Get")
// just prepend the handler(s) as middleware(s) you want to use.
// or append for "done" handlers.
index.Handlers = append([]iris.Handler{cacheHandler}, index.Handlers...)
}

func (c *exampleController) Get() string {
// refresh every 10 seconds and you will see different time output.
now := time.Now().Format("Mon, Jan 02 2006 15:04:05")
return "last time executed without cache: " + now
}
2 changes: 1 addition & 1 deletion cache/LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2017 The Iris Cache Authors. All rights reserved.
Copyright (c) 2017-2018 The Iris Cache Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
Expand Down
34 changes: 8 additions & 26 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@ Example code:
"time"

"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/cache"
)

func main(){
app := iris.Default()
cachedHandler := cache.WrapHandler(h, 2 *time.Minute)
app.Get("/hello", cachedHandler)
middleware := cache.Handler(2 *time.Minute)
app.Get("/hello", middleware, h)
app.Run(iris.Addr(":8080"))
}

func h(ctx context.Context) {
func h(ctx iris.Context) {
ctx.HTML("<h1> Hello, this should be cached. Every 2 minutes it will be refreshed, check your browser's inspector</h1>")
}
*/
Expand All @@ -32,46 +31,29 @@ import (
"github.com/kataras/iris/context"
)

// Cache 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
// Cache accepts the cache expiration duration
// if the expiration <=2 seconds then expiration is taken by the "cache-control's maxage" header
// returns context.Handler, which you can use as your default router or per-route handler
//
// All types of response can be cached, templates, json, text, anything.
//
// You can add validators with this function.
func Cache(bodyHandler context.Handler, expiration time.Duration) *client.Handler {
return client.NewHandler(bodyHandler, expiration)
}

// 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
// returns context.Handler, which you can use as your default router or per-route handler
//
// All types of response can be cached, templates, json, text, anything.
//
// 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
func Cache(expiration time.Duration) *client.Handler {
return client.NewHandler(expiration)
}

// Handler accepts one single parameter:
// the cache Entry's expiration duration
// the cache 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 types of response can be cached, templates, json, text, anything.
//
// it returns a context.Handler which can be used as a middleware, for more options use the `Cache`.
//
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/#caching
func Handler(expiration time.Duration) context.Handler {
h := WrapHandler(nil, expiration)
h := Cache(expiration).ServeHTTP
return h
}

Expand Down
70 changes: 42 additions & 28 deletions cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ var (
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)
func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBodyStr string, nocache string) error {
e.GET(path).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)
e.GET(path).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
Expand All @@ -35,19 +35,19 @@ func runTest(e *httpexpect.Expect, counterPtr *uint32, expectedBodyStr string, n
time.Sleep(cacheDuration)

// cache should be cleared now
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
e.GET(path).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)
e.GET(path).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 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)
e.GET(path).WithHeader("max-age", "0").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
e.GET(path).WithHeader("Authorization", "basic or anything").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
counter = atomic.LoadUint32(counterPtr)
if counter != 4 {
return errTestFailed.Format(4, counter)
Expand All @@ -71,8 +71,8 @@ func runTest(e *httpexpect.Expect, counterPtr *uint32, expectedBodyStr string, n
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)
// let's call again the path the expiration is not passed so it should be cached
e.GET(path).Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
counter = atomic.LoadUint32(counterPtr)
if counter != 6 {
return errTestFailed.Format(6, counter)
Expand All @@ -88,19 +88,19 @@ func TestNoCache(t *testing.T) {
app := iris.New()
var n uint32

app.Get("/", cache.WrapHandler(func(ctx context.Context) {
app.Get("/", cache.Handler(cacheDuration), func(ctx context.Context) {
atomic.AddUint32(&n, 1)
ctx.Write([]byte(expectedBodyStr))
}, cacheDuration))
})

app.Get("/nocache", cache.WrapHandler(func(ctx context.Context) {
app.Get("/nocache", cache.Handler(cacheDuration), 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 {
if err := runTest(e, "/", &n, expectedBodyStr, "/nocache"); err != nil {
t.Fatalf(t.Name()+": %v", err)
}

Expand All @@ -117,11 +117,25 @@ func TestCache(t *testing.T) {
ctx.Write([]byte(expectedBodyStr))
})

var (
n2 uint32
expectedBodyStr2 = "This is the other"
)

app.Get("/other", func(ctx context.Context) {
atomic.AddUint32(&n2, 1)
ctx.Write([]byte(expectedBodyStr2))
})

e := httptest.New(t, app)
if err := runTest(e, &n, expectedBodyStr, ""); err != nil {
if err := runTest(e, "/", &n, expectedBodyStr, ""); err != nil {
t.Fatalf(t.Name()+": %v", err)
}

if err := runTest(e, "/other", &n2, expectedBodyStr2, ""); err != nil {
t.Fatalf(t.Name()+" other: %v", err)
}

}

func TestCacheHandlerParallel(t *testing.T) {
Expand All @@ -138,10 +152,10 @@ func TestCacheValidator(t *testing.T) {
ctx.Write([]byte(expectedBodyStr))
}

validCache := cache.Cache(h, cacheDuration)
app.Get("/", validCache.ServeHTTP)
validCache := cache.Cache(cacheDuration)
app.Get("/", validCache.ServeHTTP, h)

managedCache := cache.Cache(h, cacheDuration)
managedCache := cache.Cache(cacheDuration)
managedCache.AddRule(rule.Validator([]rule.PreValidator{
func(ctx context.Context) bool {
if ctx.Request().URL.Path == "/invalid" {
Expand All @@ -151,12 +165,7 @@ func TestCacheValidator(t *testing.T) {
},
}, 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 := cache.Cache(cacheDuration)
managedCache2.AddRule(rule.Validator(nil,
[]rule.PostValidator{
func(ctx context.Context) bool {
Expand All @@ -168,10 +177,15 @@ func TestCacheValidator(t *testing.T) {
},
))

app.Get("/valid", validCache.ServeHTTP)
app.Get("/valid", validCache.ServeHTTP, h)

app.Get("/invalid", managedCache.ServeHTTP, h)
app.Get("/invalid2", managedCache2.ServeHTTP, 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))

app.Get("/invalid", managedCache.ServeHTTP)
app.Get("/invalid2", managedCache2.ServeHTTP)
})

e := httptest.New(t, app)

Expand Down
Loading