Skip to content

Commit

Permalink
Merge pull request #890 from kataras/dev
Browse files Browse the repository at this point in the history
New Cache Middleware: iris.Cache304
  • Loading branch information
kataras authored Jan 28, 2018
2 parents 47108dc + a39e3d7 commit 1722355
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 173 deletions.
1 change: 1 addition & 0 deletions _examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ The `httptest` package is your way for end-to-end HTTP testing, it uses the http
iris cache library lives on its own [package](https://github.com/kataras/iris/tree/master/cache).

- [Simple](cache/simple/main.go)
- [Client-Side (304)](cache/client-side/main.go) - part of the iris context core

> You're free to use your own favourite caching package if you'd like so.

Expand Down
39 changes: 39 additions & 0 deletions _examples/cache/client-side/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Package main shows how you can use the `WriteWithExpiration`
// based on the "modtime", if it's newer than the request header then
// it will refresh the contents, otherwise will let the client (99.9% the browser)
// to handle the cache mechanism, it's faster than iris.Cache because server-side
// has nothing to do and no need to store the responses in the memory.
package main

import (
"time"

"github.com/kataras/iris"
)

const refreshEvery = 10 * time.Second

func main() {
app := iris.New()
app.Use(iris.Cache304(refreshEvery))
// same as:
// app.Use(func(ctx iris.Context) {
// now := time.Now()
// if modified, err := ctx.CheckIfModifiedSince(now.Add(-refresh)); !modified && err == nil {
// ctx.WriteNotModified()
// return
// }

// ctx.SetLastModified(now)

// ctx.Next()
// })

app.Get("/", greet)
app.Run(iris.Addr(":8080"))
}

func greet(ctx iris.Context) {
ctx.Header("X-Custom", "my custom header")
ctx.Writef("Hello World! %s", time.Now())
}
5 changes: 5 additions & 0 deletions _examples/cache/simple/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@ func writeMarkdown(ctx iris.Context) {

ctx.Markdown(markdownContents)
}

/* Note that `StaticWeb` does use the browser's disk caching by-default
therefore, register the cache handler AFTER any StaticWeb 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 */
2 changes: 1 addition & 1 deletion _examples/experimental-handlers/csrf/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func main() {
}

func getSignupForm(ctx iris.Context) {
// views/signup.html just needs a {{ .csrfField }} template tag for
// views/user/signup.html just needs a {{ .csrfField }} template tag for
// csrf.TemplateField to inject the CSRF token into. Easy!
ctx.ViewData(csrf.TemplateTag, csrf.TemplateField(ctx))
ctx.View("user/signup.html")
Expand Down
11 changes: 10 additions & 1 deletion cache/cache.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/* Package cache provides cache capabilities with rich support of options and rules.
/* Package cache provides server-side caching capabilities with rich support of options and rules.
Use it for server-side caching, see the `iris#Cache304` for an alternative approach that
may fit your needs most.
Example code:
Expand Down Expand Up @@ -37,6 +40,9 @@ import (
//
// All types of response can be cached, templates, json, text, anything.
//
// Use it for server-side caching, see the `iris#Cache304` for an alternative approach that
// may fit your needs most.
//
// You can add validators with this function.
func Cache(expiration time.Duration) *client.Handler {
return client.NewHandler(expiration)
Expand All @@ -49,6 +55,9 @@ func Cache(expiration time.Duration) *client.Handler {
//
// All types of response can be cached, templates, json, text, anything.
//
// Use it for server-side caching, see the `iris#Cache304` for an alternative approach that
// may fit your needs most.
//
// 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
Expand Down
21 changes: 16 additions & 5 deletions cache/client/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"sync"
"time"

"github.com/kataras/iris/cache/cfg"
"github.com/kataras/iris/cache/client/rule"
"github.com/kataras/iris/cache/entry"
"github.com/kataras/iris/context"
Expand Down Expand Up @@ -66,6 +65,12 @@ var emptyHandler = func(ctx context.Context) {
ctx.StopExecution()
}

func parseLifeChanger(ctx context.Context) entry.LifeChanger {
return func() time.Duration {
return time.Duration(ctx.MaxAge()) * time.Second
}
}

///TODO: debug this and re-run the parallel tests on larger scale,
// because I think we have a bug here when `core/router#StaticWeb` is used after this middleware.
func (h *Handler) ServeHTTP(ctx context.Context) {
Expand Down Expand Up @@ -135,14 +140,19 @@ func (h *Handler) ServeHTTP(ctx context.Context) {
// no need to copy the body, its already done inside
body := recorder.Body()
if len(body) == 0 {
// if no body then just exit
// if no body then just exit.
return
}

// check for an expiration time if the
// given expiration was not valid then check for GetMaxAge &
// update the response & release the recorder
e.Reset(recorder.StatusCode(), recorder.Header().Get(cfg.ContentTypeHeader), body, GetMaxAge(ctx.Request()))
e.Reset(
recorder.StatusCode(),
recorder.Header(),
body,
parseLifeChanger(ctx),
)

// fmt.Printf("reset cache entry\n")
// fmt.Printf("key: %s\n", key)
Expand All @@ -152,12 +162,13 @@ func (h *Handler) ServeHTTP(ctx context.Context) {
}

// if it's valid then just write the cached results
ctx.ContentType(response.ContentType())
entry.CopyHeaders(ctx.ResponseWriter().Header(), response.Headers())
ctx.SetLastModified(e.LastModified)
ctx.StatusCode(response.StatusCode())
ctx.Write(response.Body())

// fmt.Printf("key: %s\n", key)
// fmt.Printf("write content type: %s\n", response.ContentType())
// fmt.Printf("write content type: %s\n", response.Headers()["ContentType"])
// fmt.Printf("write body len: %d\n", len(response.Body()))

}
109 changes: 0 additions & 109 deletions cache/client/response_recorder.go

This file was deleted.

20 changes: 0 additions & 20 deletions cache/client/utils.go

This file was deleted.

31 changes: 27 additions & 4 deletions cache/entry/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ type Entry struct {
// ExpiresAt is the time which this cache will not be available
expiresAt time.Time

// when `Reset` this value is reseting to time.Now(),
// it's used to send the "Last-Modified" header,
// some clients may need it.
LastModified time.Time

// Response the response should be served to the client
response *Response
// but we need the key to invalidate manually...xmm
Expand Down Expand Up @@ -78,10 +83,23 @@ func (e *Entry) ChangeLifetime(fdur LifeChanger) {
}
}

// CopyHeaders clones headers "src" to "dst" .
func CopyHeaders(dst map[string][]string, src map[string][]string) {
if dst == nil || src == nil {
return
}

for k, vv := range src {
v := make([]string, len(vv))
copy(v, vv)
dst[k] = v
}
}

// Reset called each time the entry is expired
// and the handler calls this after the original handler executed
// to re-set the response with the new handler's content result
func (e *Entry) Reset(statusCode int, contentType string,
func (e *Entry) Reset(statusCode int, headers map[string][]string,
body []byte, lifeChanger LifeChanger) {

if e.response == nil {
Expand All @@ -91,8 +109,10 @@ func (e *Entry) Reset(statusCode int, contentType string,
e.response.statusCode = statusCode
}

if contentType != "" {
e.response.contentType = contentType
if len(headers) > 0 {
newHeaders := make(map[string][]string, len(headers))
CopyHeaders(newHeaders, headers)
e.response.headers = newHeaders
}

e.response.body = body
Expand All @@ -101,5 +121,8 @@ func (e *Entry) Reset(statusCode int, contentType string,
if lifeChanger != nil {
e.ChangeLifetime(lifeChanger)
}
e.expiresAt = time.Now().Add(e.life)

now := time.Now()
e.expiresAt = now.Add(e.life)
e.LastModified = now
}
31 changes: 19 additions & 12 deletions cache/entry/response.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package entry

import "net/http"

// Response is the cached response will be send to the clients
// its fields setted at runtime on each of the non-cached executions
// non-cached executions = first execution, and each time after
// cache expiration datetime passed
// cache expiration datetime passed.
type Response struct {
// statusCode for the response cache handler
// statusCode for the response cache handler.
statusCode int
// contentType for the response cache handler
contentType string
// body is the contents will be served by the cache handler
// body is the contents will be served by the cache handler.
body []byte
// the total headers of the response, including content type.
headers http.Header
}

// StatusCode returns a valid status code
// StatusCode returns a valid status code.
func (r *Response) StatusCode() int {
if r.statusCode <= 0 {
r.statusCode = 200
Expand All @@ -22,14 +24,19 @@ func (r *Response) StatusCode() int {
}

// ContentType returns a valid content type
func (r *Response) ContentType() string {
if r.contentType == "" {
r.contentType = "text/html; charset=utf-8"
}
return r.contentType
// func (r *Response) ContentType() string {
// if r.headers == "" {
// r.contentType = "text/html; charset=utf-8"
// }
// return r.contentType
// }

// Headers returns the total headers of the cached response.
func (r *Response) Headers() http.Header {
return r.headers
}

// Body returns contents will be served by the cache handler
// Body returns contents will be served by the cache handler.
func (r *Response) Body() []byte {
return r.body
}
Loading

0 comments on commit 1722355

Please sign in to comment.