mirror of
https://github.com/kataras/iris.git
synced 2026-01-04 10:47:20 +00:00
some major improvements to the (server-side) cache middleware and an example of a client-side responsibility cache
Former-commit-id: 93d3a7a6f163c6d49f315f86d10e63f7b1b1d93a
This commit is contained in:
21
cache/client/handler.go
vendored
21
cache/client/handler.go
vendored
@@ -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"
|
||||
@@ -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) {
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
context.SetLastModified(ctx, 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
cache/client/response_recorder.go
vendored
109
cache/client/response_recorder.go
vendored
@@ -1,109 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var rpool = sync.Pool{}
|
||||
|
||||
// AcquireResponseRecorder returns a ResponseRecorder
|
||||
func AcquireResponseRecorder(underline http.ResponseWriter) *ResponseRecorder {
|
||||
v := rpool.Get()
|
||||
var res *ResponseRecorder
|
||||
if v != nil {
|
||||
res = v.(*ResponseRecorder)
|
||||
} else {
|
||||
res = &ResponseRecorder{}
|
||||
}
|
||||
res.underline = underline
|
||||
return res
|
||||
}
|
||||
|
||||
// ReleaseResponseRecorder releases a ResponseRecorder which has been previously received by AcquireResponseRecorder
|
||||
func ReleaseResponseRecorder(res *ResponseRecorder) {
|
||||
res.underline = nil
|
||||
res.statusCode = 0
|
||||
res.chunks = res.chunks[0:0]
|
||||
rpool.Put(res)
|
||||
}
|
||||
|
||||
// ResponseRecorder is used by httpcache to be able to get the Body and the StatusCode of a request handler
|
||||
type ResponseRecorder struct {
|
||||
underline http.ResponseWriter
|
||||
chunks [][]byte // 2d because .Write can be called more than one time in the same handler and we want to cache all of them
|
||||
statusCode int // the saved status code which will be used from the cache service
|
||||
}
|
||||
|
||||
// Body joins the chunks to one []byte slice, this is the full body
|
||||
func (res *ResponseRecorder) Body() []byte {
|
||||
var body []byte
|
||||
for i := range res.chunks {
|
||||
body = append(body, res.chunks[i]...)
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
// ContentType returns the header's value of "Content-Type"
|
||||
func (res *ResponseRecorder) ContentType() string {
|
||||
return res.Header().Get("Content-Type")
|
||||
}
|
||||
|
||||
// StatusCode returns the status code, if not given then returns 200
|
||||
// but doesn't changes the existing behavior
|
||||
func (res *ResponseRecorder) StatusCode() int {
|
||||
if res.statusCode == 0 {
|
||||
return 200
|
||||
}
|
||||
return res.statusCode
|
||||
}
|
||||
|
||||
// Header returns the header map that will be sent by
|
||||
// WriteHeader. Changing the header after a call to
|
||||
// WriteHeader (or Write) has no effect unless the modified
|
||||
// headers were declared as trailers by setting the
|
||||
// "Trailer" header before the call to WriteHeader (see example).
|
||||
// To suppress implicit response headers, set their value to nil.
|
||||
func (res *ResponseRecorder) Header() http.Header {
|
||||
return res.underline.Header()
|
||||
}
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply.
|
||||
//
|
||||
// If WriteHeader has not yet been called, Write calls
|
||||
// WriteHeader(http.StatusOK) before writing the data. If the Header
|
||||
// does not contain a Content-Type line, Write adds a Content-Type set
|
||||
// to the result of passing the initial 512 bytes of written data to
|
||||
// DetectContentType.
|
||||
//
|
||||
// Depending on the HTTP protocol version and the client, calling
|
||||
// Write or WriteHeader may prevent future reads on the
|
||||
// Request.Body. For HTTP/1.x requests, handlers should read any
|
||||
// needed request body data before writing the response. Once the
|
||||
// headers have been flushed (due to either an explicit Flusher.Flush
|
||||
// call or writing enough data to trigger a flush), the request body
|
||||
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
|
||||
// handlers to continue to read the request body while concurrently
|
||||
// writing the response. However, such behavior may not be supported
|
||||
// by all HTTP/2 clients. Handlers should read before writing if
|
||||
// possible to maximize compatibility.
|
||||
func (res *ResponseRecorder) Write(contents []byte) (int, error) {
|
||||
if res.statusCode == 0 { // if not setted set it here
|
||||
res.WriteHeader(http.StatusOK)
|
||||
}
|
||||
res.chunks = append(res.chunks, contents)
|
||||
return res.underline.Write(contents)
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code.
|
||||
// If WriteHeader is not called explicitly, the first call to Write
|
||||
// will trigger an implicit WriteHeader(http.StatusOK).
|
||||
// Thus explicit calls to WriteHeader are mainly used to
|
||||
// send error codes.
|
||||
func (res *ResponseRecorder) WriteHeader(statusCode int) {
|
||||
if res.statusCode == 0 { // set it only if not setted already, we don't want logs about multiple sends
|
||||
res.statusCode = statusCode
|
||||
res.underline.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
}
|
||||
20
cache/client/utils.go
vendored
20
cache/client/utils.go
vendored
@@ -1,20 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/cache/entry"
|
||||
)
|
||||
|
||||
// GetMaxAge parses the "Cache-Control" header
|
||||
// and returns a LifeChanger which can be passed
|
||||
// to the response's Reset
|
||||
func GetMaxAge(r *http.Request) entry.LifeChanger {
|
||||
return func() time.Duration {
|
||||
cacheControlHeader := r.Header.Get("Cache-Control")
|
||||
// headerCacheDur returns the seconds
|
||||
headerCacheDur := entry.ParseMaxAge(cacheControlHeader)
|
||||
return time.Duration(headerCacheDur) * time.Second
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user