Skip to content

Commit

Permalink
fix #1621
Browse files Browse the repository at this point in the history
  • Loading branch information
kataras committed Sep 10, 2020
1 parent 333be42 commit cf0338d
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 40 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
86 changes: 52 additions & 34 deletions _examples/response-writer/cache/simple/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------
Expand All @@ -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.
Expand All @@ -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")
}

Expand All @@ -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. */
12 changes: 12 additions & 0 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
50 changes: 44 additions & 6 deletions cache/client/handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package client

import (
"strings"
"sync"
"time"

Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down

0 comments on commit cf0338d

Please sign in to comment.