mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 18:51:03 +01:00
Merge pull request #852 from kataras/dev
make cache package to work across multi handlers. Former-commit-id: 2aafeb75f093c973d0b05b7f94c302f64b4b70e6
This commit is contained in:
commit
e30862b74b
|
@ -34,9 +34,9 @@ func (r resource) loadFromBase(dir string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
result := string(b)
|
result := string(b)
|
||||||
//if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
// result = strings.Replace(result, "\n", "\r\n", -1)
|
result = strings.Replace(result, "\n", "\r\n", -1)
|
||||||
//}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ func (r resource) loadFromBase(dir string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
result := string(b)
|
result := string(b)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -34,9 +35,9 @@ func (r resource) loadFromBase(dir string) string {
|
||||||
panic(fullpath + " failed with error: " + err.Error())
|
panic(fullpath + " failed with error: " + err.Error())
|
||||||
}
|
}
|
||||||
result := string(b)
|
result := string(b)
|
||||||
// if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
// result = strings.Replace(result, "\n", "\r\n", -1)
|
result = strings.Replace(result, "\n", "\r\n", -1)
|
||||||
// }
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
105
_examples/mvc/cache/main.go
vendored
Normal file
105
_examples/mvc/cache/main.go
vendored
Normal file
|
@ -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
cache/LICENSE
vendored
2
cache/LICENSE
vendored
|
@ -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
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
|
|
34
cache/cache.go
vendored
34
cache/cache.go
vendored
|
@ -7,18 +7,17 @@ Example code:
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kataras/iris"
|
"github.com/kataras/iris"
|
||||||
"github.com/kataras/iris/context"
|
|
||||||
"github.com/kataras/iris/cache"
|
"github.com/kataras/iris/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main(){
|
func main(){
|
||||||
app := iris.Default()
|
app := iris.Default()
|
||||||
cachedHandler := cache.WrapHandler(h, 2 *time.Minute)
|
middleware := cache.Handler(2 *time.Minute)
|
||||||
app.Get("/hello", cachedHandler)
|
app.Get("/hello", middleware, h)
|
||||||
app.Run(iris.Addr(":8080"))
|
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>")
|
ctx.HTML("<h1> Hello, this should be cached. Every 2 minutes it will be refreshed, check your browser's inspector</h1>")
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
@ -32,46 +31,29 @@ import (
|
||||||
"github.com/kataras/iris/context"
|
"github.com/kataras/iris/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cache accepts two parameters
|
// Cache accepts the cache expiration duration
|
||||||
// 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
|
// 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
|
// 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.
|
// All types of response can be 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 {
|
func Cache(expiration time.Duration) *client.Handler {
|
||||||
return client.NewHandler(bodyHandler, expiration)
|
return client.NewHandler(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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler accepts one single parameter:
|
// 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
|
// if the expiration <=2 seconds then expiration is taken by the "cache-control's maxage" header
|
||||||
// returns context.Handler.
|
// 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.
|
// 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`.
|
// 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
|
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/#caching
|
||||||
func Handler(expiration time.Duration) context.Handler {
|
func Handler(expiration time.Duration) context.Handler {
|
||||||
h := WrapHandler(nil, expiration)
|
h := Cache(expiration).ServeHTTP
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
70
cache/cache_test.go
vendored
70
cache/cache_test.go
vendored
|
@ -23,10 +23,10 @@ var (
|
||||||
errTestFailed = errors.New("expected the main handler to be executed %d times instead of %d")
|
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 {
|
func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBodyStr string, nocache string) error {
|
||||||
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
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
|
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)
|
counter := atomic.LoadUint32(counterPtr)
|
||||||
if counter > 1 {
|
if counter > 1 {
|
||||||
// n should be 1 because it doesn't changed after the first call
|
// n should be 1 because it doesn't changed after the first call
|
||||||
|
@ -35,19 +35,19 @@ func runTest(e *httpexpect.Expect, counterPtr *uint32, expectedBodyStr string, n
|
||||||
time.Sleep(cacheDuration)
|
time.Sleep(cacheDuration)
|
||||||
|
|
||||||
// cache should be cleared now
|
// 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)
|
time.Sleep(cacheDuration / 5)
|
||||||
// let's call again , the cache should be saved
|
// 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)
|
counter = atomic.LoadUint32(counterPtr)
|
||||||
if counter != 2 {
|
if counter != 2 {
|
||||||
return errTestFailed.Format(2, counter)
|
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
|
// 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(path).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("Authorization", "basic or anything").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||||
counter = atomic.LoadUint32(counterPtr)
|
counter = atomic.LoadUint32(counterPtr)
|
||||||
if counter != 4 {
|
if counter != 4 {
|
||||||
return errTestFailed.Format(4, counter)
|
return errTestFailed.Format(4, counter)
|
||||||
|
@ -71,8 +71,8 @@ func runTest(e *httpexpect.Expect, counterPtr *uint32, expectedBodyStr string, n
|
||||||
return errTestFailed.Format(6, counter)
|
return errTestFailed.Format(6, counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// let's call again the "/", the expiration is not passed so it should be cached
|
// let's call again the path the expiration is not passed so it should be cached
|
||||||
e.GET("/").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
e.GET(path).Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr)
|
||||||
counter = atomic.LoadUint32(counterPtr)
|
counter = atomic.LoadUint32(counterPtr)
|
||||||
if counter != 6 {
|
if counter != 6 {
|
||||||
return errTestFailed.Format(6, counter)
|
return errTestFailed.Format(6, counter)
|
||||||
|
@ -88,19 +88,19 @@ func TestNoCache(t *testing.T) {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
var n uint32
|
var n uint32
|
||||||
|
|
||||||
app.Get("/", cache.WrapHandler(func(ctx context.Context) {
|
app.Get("/", cache.Handler(cacheDuration), func(ctx context.Context) {
|
||||||
atomic.AddUint32(&n, 1)
|
atomic.AddUint32(&n, 1)
|
||||||
ctx.Write([]byte(expectedBodyStr))
|
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) // <----
|
cache.NoCache(ctx) // <----
|
||||||
atomic.AddUint32(&n, 1)
|
atomic.AddUint32(&n, 1)
|
||||||
ctx.Write([]byte(expectedBodyStr))
|
ctx.Write([]byte(expectedBodyStr))
|
||||||
}, cacheDuration))
|
})
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
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)
|
t.Fatalf(t.Name()+": %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,11 +117,25 @@ func TestCache(t *testing.T) {
|
||||||
ctx.Write([]byte(expectedBodyStr))
|
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)
|
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)
|
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) {
|
func TestCacheHandlerParallel(t *testing.T) {
|
||||||
|
@ -138,10 +152,10 @@ func TestCacheValidator(t *testing.T) {
|
||||||
ctx.Write([]byte(expectedBodyStr))
|
ctx.Write([]byte(expectedBodyStr))
|
||||||
}
|
}
|
||||||
|
|
||||||
validCache := cache.Cache(h, cacheDuration)
|
validCache := cache.Cache(cacheDuration)
|
||||||
app.Get("/", validCache.ServeHTTP)
|
app.Get("/", validCache.ServeHTTP, h)
|
||||||
|
|
||||||
managedCache := cache.Cache(h, cacheDuration)
|
managedCache := cache.Cache(cacheDuration)
|
||||||
managedCache.AddRule(rule.Validator([]rule.PreValidator{
|
managedCache.AddRule(rule.Validator([]rule.PreValidator{
|
||||||
func(ctx context.Context) bool {
|
func(ctx context.Context) bool {
|
||||||
if ctx.Request().URL.Path == "/invalid" {
|
if ctx.Request().URL.Path == "/invalid" {
|
||||||
|
@ -151,12 +165,7 @@ func TestCacheValidator(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, nil))
|
}, nil))
|
||||||
|
|
||||||
managedCache2 := cache.Cache(func(ctx context.Context) {
|
managedCache2 := cache.Cache(cacheDuration)
|
||||||
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,
|
managedCache2.AddRule(rule.Validator(nil,
|
||||||
[]rule.PostValidator{
|
[]rule.PostValidator{
|
||||||
func(ctx context.Context) bool {
|
func(ctx context.Context) bool {
|
||||||
|
@ -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)
|
app.Get("/invalid", managedCache.ServeHTTP, h)
|
||||||
app.Get("/invalid2", managedCache2.ServeHTTP)
|
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))
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
e := httptest.New(t, app)
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
|
|
88
cache/client/handler.go
vendored
88
cache/client/handler.go
vendored
|
@ -1,6 +1,7 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kataras/iris/cache/cfg"
|
"github.com/kataras/iris/cache/cfg"
|
||||||
|
@ -10,34 +11,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler the local cache service handler contains
|
// Handler the local cache service handler contains
|
||||||
// the original bodyHandler, the memory cache entry and
|
// the original response, the memory cache entry and
|
||||||
// the validator for each of the incoming requests and post responses
|
// the validator for each of the incoming requests and post responses
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
|
||||||
// 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
|
// Rule optional validators for pre cache and post cache actions
|
||||||
//
|
//
|
||||||
// See more at ruleset.go
|
// See more at ruleset.go
|
||||||
rule rule.Rule
|
rule rule.Rule
|
||||||
|
// when expires.
|
||||||
// entry is the memory cache entry
|
expiration time.Duration
|
||||||
entry *entry.Entry
|
// entries the memory cache stored responses.
|
||||||
|
entries map[string]*entry.Entry
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler returns a new cached handler for the "bodyHandler"
|
// NewHandler returns a new cached handler for the "bodyHandler"
|
||||||
// which expires every "expiration".
|
// which expires every "expiration".
|
||||||
func NewHandler(bodyHandler context.Handler,
|
func NewHandler(expiration time.Duration) *Handler {
|
||||||
expiration time.Duration) *Handler {
|
|
||||||
|
|
||||||
e := entry.NewEntry(expiration)
|
|
||||||
|
|
||||||
return &Handler{
|
return &Handler{
|
||||||
bodyHandler: bodyHandler,
|
rule: DefaultRuleSet,
|
||||||
rule: DefaultRuleSet,
|
expiration: expiration,
|
||||||
entry: e,
|
entries: make(map[string]*entry.Entry, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,35 +60,53 @@ func (h *Handler) AddRule(r rule.Rule) *Handler {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var emptyHandler = func(ctx context.Context) {
|
||||||
|
ctx.StatusCode(500)
|
||||||
|
ctx.WriteString("cache: empty body handler")
|
||||||
|
ctx.StopExecution()
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) ServeHTTP(ctx context.Context) {
|
func (h *Handler) ServeHTTP(ctx context.Context) {
|
||||||
// check for pre-cache validators, if at least one of them return false
|
// check for pre-cache validators, if at least one of them return false
|
||||||
// for this specific request, then skip the whole cache
|
// for this specific request, then skip the whole cache
|
||||||
bodyHandler := h.bodyHandler
|
bodyHandler := ctx.NextHandler()
|
||||||
|
|
||||||
if bodyHandler == nil {
|
if bodyHandler == nil {
|
||||||
if nextHandler := ctx.NextHandler(); nextHandler != nil {
|
emptyHandler(ctx)
|
||||||
// skip prepares the context to move to the next handler if the "nextHandler" has a ctx.Next() inside it,
|
return
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 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()
|
||||||
|
|
||||||
if !h.rule.Claim(ctx) {
|
if !h.rule.Claim(ctx) {
|
||||||
bodyHandler(ctx)
|
bodyHandler(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we have a stored response( it is not expired)
|
var (
|
||||||
res, exists := h.entry.Response()
|
response *entry.Response
|
||||||
if !exists {
|
valid = false
|
||||||
|
key = ctx.Path()
|
||||||
|
)
|
||||||
|
|
||||||
// if it's not exists, then execute the original handler
|
h.mu.RLock()
|
||||||
|
e, found := h.entries[key]
|
||||||
|
h.mu.RUnlock()
|
||||||
|
|
||||||
|
if found {
|
||||||
|
// the entry is here, .Response will give us
|
||||||
|
// if it's expired or no
|
||||||
|
response, valid = e.Response()
|
||||||
|
} else {
|
||||||
|
// create the entry now.
|
||||||
|
e = entry.NewEntry(h.expiration)
|
||||||
|
h.mu.Lock()
|
||||||
|
h.entries[key] = e
|
||||||
|
h.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
// if it's expired, then execute the original handler
|
||||||
// with our custom response recorder response writer
|
// with our custom response recorder response writer
|
||||||
// because the net/http doesn't give us
|
// because the net/http doesn't give us
|
||||||
// a built'n way to get the status code & body
|
// a built'n way to get the status code & body
|
||||||
|
@ -119,12 +131,12 @@ func (h *Handler) ServeHTTP(ctx context.Context) {
|
||||||
// check for an expiration time if the
|
// check for an expiration time if the
|
||||||
// given expiration was not valid then check for GetMaxAge &
|
// given expiration was not valid then check for GetMaxAge &
|
||||||
// update the response & release the recorder
|
// update the response & release the recorder
|
||||||
h.entry.Reset(recorder.StatusCode(), recorder.Header().Get(cfg.ContentTypeHeader), body, GetMaxAge(ctx.Request()))
|
e.Reset(recorder.StatusCode(), recorder.Header().Get(cfg.ContentTypeHeader), body, GetMaxAge(ctx.Request()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it's valid then just write the cached results
|
// if it's valid then just write the cached results
|
||||||
ctx.ContentType(res.ContentType())
|
ctx.ContentType(response.ContentType())
|
||||||
ctx.StatusCode(res.StatusCode())
|
ctx.StatusCode(response.StatusCode())
|
||||||
ctx.Write(res.Body())
|
ctx.Write(response.Body())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user