From cf0338d34218403ea3ab79862382b13354ba4221 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 10 Sep 2020 11:24:23 +0300 Subject: [PATCH] fix https://github.com/kataras/iris/issues/1621 --- HISTORY.md | 2 + .../response-writer/cache/simple/main.go | 86 +++++++++++-------- cache/cache.go | 12 +++ cache/client/handler.go | 50 +++++++++-- 4 files changed, 110 insertions(+), 40 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index e97e58d838..37dab6ee37 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -364,6 +364,8 @@ Response: Other Improvements: +- Fix [#1621](https://github.com/kataras/iris/issues/1621) and add a new `cache.WithKey` to customize the cached entry key. + - Add a `Response() *http.Response` to the Response Recorder. - Fix Response Recorder `Flush` when transfer-encoding is `chunked`. - Fix Response Recorder `Clone` concurrent access afterwards. diff --git a/_examples/response-writer/cache/simple/main.go b/_examples/response-writer/cache/simple/main.go index 010c351116..67903641cd 100644 --- a/_examples/response-writer/cache/simple/main.go +++ b/_examples/response-writer/cache/simple/main.go @@ -4,16 +4,14 @@ import ( "time" "github.com/kataras/iris/v12" - "github.com/kataras/iris/v12/cache" + "github.com/kataras/iris/v12/middleware/basicauth" ) var markdownContents = []byte(`## Hello Markdown This is a sample of Markdown contents - - Features -------- @@ -23,36 +21,7 @@ All features of Sundown are supported, including: the --tidy option. Without --tidy, the differences are mostly in whitespace and entity escaping, where blackfriday is more consistent and cleaner. - -* **Common extensions**, including table support, fenced code - blocks, autolinks, strikethroughs, non-strict emphasis, etc. - -* **Safety**. Blackfriday is paranoid when parsing, making it safe - to feed untrusted user input without fear of bad things - happening. The test suite stress tests this and there are no - known inputs that make it crash. If you find one, please let me - know and send me the input that does it. - - NOTE: "safety" in this context means *runtime safety only*. In order to - protect yourself against JavaScript injection in untrusted content, see - [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content). - -* **Fast processing**. It is fast enough to render on-demand in - most web applications without having to cache the output. - -* **Routine safety**. You can run multiple parsers in different - goroutines without ill effect. There is no dependence on global - shared state. - -* **Minimal dependencies**. Blackfriday only depends on standard - library packages in Go. The source code is pretty - self-contained, so it is easy to add to any project, including - Google App Engine projects. - -* **Standards compliant**. Output successfully validates using the - W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional. - - [this is a link](https://github.com/kataras/iris) `) +`) // Cache should not be used on handlers that contain dynamic data. // Cache is a good and a must-feature on static content, i.e "about page" or for a whole blog site. @@ -63,6 +32,30 @@ func main() { // saves its content on the first request and serves it instead of re-calculating the content. // After 10 seconds it will be cleared and reset. + pages := app.Party("/pages") + pages.Use(cache.Handler(10 * time.Second)) // Per Party. + pages.Get("/", pagesIndex) + pages.Post("/", pagesIndexPost) + + // Note: on authenticated requests + // the cache middleare does not run at all (see iris/cache/ruleset). + auth := basicauth.Default(map[string]string{ + "admin": "admin", + }) + app.Get("/protected", auth, cache.Handler(5*time.Second), protected) + + // Set custom cache key/identifier, + // for the sake of the example + // we will SHARE the keys on both GET and POST routes + // so the first one is executed that's the body + // for both of the routes. Please don't do that + // on production, this is just an example. + custom := app.Party("/custom") + custom.Use(cache.WithKey("shared")) + custom.Use(cache.Handler(10 * time.Second)) + custom.Get("/", customIndex) + custom.Post("/", customIndexPost) + app.Listen(":8080") } @@ -74,7 +67,32 @@ func writeMarkdown(ctx iris.Context) { ctx.Markdown(markdownContents) } +func pagesIndex(ctx iris.Context) { + println("Handler executed. Content refreshed.") + ctx.WriteString("GET: hello") +} + +func pagesIndexPost(ctx iris.Context) { + println("Handler executed. Content refreshed.") + ctx.WriteString("POST: hello") +} + +func protected(ctx iris.Context) { + username, _, _ := ctx.Request().BasicAuth() + ctx.Writef("Hello, %s!", username) +} + +func customIndex(ctx iris.Context) { + ctx.WriteString("Contents from GET custom index") +} + +func customIndexPost(ctx iris.Context) { + ctx.WriteString("Contents from POST custom index") +} + /* Note that `HandleDir` does use the browser's disk caching by-default therefore, register the cache handler AFTER any HandleDir calls, for a faster solution that server doesn't need to keep track of the response -navigate to https://github.com/kataras/iris/blob/master/_examples/cache/client-side/main.go */ +navigate to https://github.com/kataras/iris/blob/master/_examples/cache/client-side/main.go. + +The `HandleDir` has its own cache mechanism, read the 'file-server' examples. */ diff --git a/cache/cache.go b/cache/cache.go index 9babe5c101..5c4067196d 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -34,6 +34,18 @@ import ( "github.com/kataras/iris/v12/context" ) +// WithKey sets a custom entry key for cached pages. +// Should be prepended to the cache handler. +// +// Usage: +// app.Get("/", cache.WithKey("custom-key"), cache.Handler(time.Minute), mainHandler) +func WithKey(key string) context.Handler { + return func(ctx *context.Context) { + client.SetKey(ctx, key) + ctx.Next() + } +} + // Cache accepts the cache expiration duration. // If the "expiration" input argument is invalid, <=2 seconds, // then expiration is taken by the "cache-control's maxage" header. diff --git a/cache/client/handler.go b/cache/client/handler.go index ffeae084ca..202d07e5b4 100644 --- a/cache/client/handler.go +++ b/cache/client/handler.go @@ -1,6 +1,7 @@ package client import ( + "strings" "sync" "time" @@ -73,6 +74,48 @@ func parseLifeChanger(ctx *context.Context) entry.LifeChanger { } } +const entryKeyContextKey = "iris.cache.server.entry.key" + +// SetKey sets a custom entry key for cached pages. +// See root package-level `WithKey` instead. +func SetKey(ctx *context.Context, key string) { + ctx.Values().Set(entryKeyContextKey, key) +} + +// GetKey returns the entry key for the current page. +func GetKey(ctx *context.Context) string { + return ctx.Values().GetString(entryKeyContextKey) +} + +func getOrSetKey(ctx *context.Context) string { + if key := GetKey(ctx); key != "" { + return key + } + + // Note: by-default the rules(ruleset pkg) + // explictly ignores the cache handler + // execution on authenticated requests + // and immediately runs the next handler: + // if !h.rule.Claim(ctx) ...see `Handler` method. + // So the below two lines are useless, + // however we add it for cases + // that the end-developer messedup with the rules + // and by accident allow authenticated cached results. + username, password, _ := ctx.Request().BasicAuth() + authPart := username + strings.Repeat("*", len(password)) + + key := ctx.Method() + authPart + + u := ctx.Request().URL + if !u.IsAbs() { + key += ctx.Scheme() + ctx.Host() + } + key += u.String() + + SetKey(ctx, key) + return key +} + 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 @@ -90,16 +133,11 @@ func (h *Handler) ServeHTTP(ctx *context.Context) { return } - scheme := "http" - if ctx.Request().TLS != nil { - scheme = "https" - } - var ( response *entry.Response valid = false // unique per subdomains and paths with different url query. - key = scheme + ctx.Host() + ctx.Request().URL.RequestURI() + key = getOrSetKey(ctx) ) h.mu.RLock()