1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-24 05:17:03 +00:00

add Cache304 as an alternative to the server-side kataras/iris/cache middleware - it can perform better with less server overheat but it comes with a cost of 304 instead of 200 so custom clients must make that check

Former-commit-id: b0ba68c528c870fe060e2825c35689771a1d3680
This commit is contained in:
Gerasimos (Makis) Maropoulos
2018-01-25 16:19:45 +02:00
parent befb1f0c08
commit 969c2e87d4
8 changed files with 140 additions and 30 deletions

View File

@@ -639,6 +639,39 @@ type Context interface {
//
// Returns the number of bytes written and any write error encountered.
WriteString(body string) (int, error)
// SetLastModified sets the "Last-Modified" based on the "modtime" input.
// If "modtime" is zero then it does nothing.
//
// It's mostly internally on core/router and context packages.
//
// Note that modtime.UTC() is being used instead of just modtime, so
// you don't have to know the internals in order to make that works.
SetLastModified(modtime time.Time)
// CheckIfModifiedSince checks if the response is modified since the "modtime".
// Note that it has nothing to do with server-side caching.
// It does those checks by checking if the "If-Modified-Since" request header
// sent by client or a previous server response header
// (e.g with WriteWithExpiration or StaticEmbedded or Favicon etc.)
// is a valid one and it's before the "modtime".
//
// A check for !modtime && err == nil is necessary to make sure that
// it's not modified since, because it may return false but without even
// had the chance to check the client-side (request) header due to some errors,
// like the HTTP Method is not "GET" or "HEAD" or if the "modtime" is zero
// or if parsing time from the header failed.
//
// It's mostly used internally, e.g. `context#WriteWithExpiration`.
//
// Note that modtime.UTC() is being used instead of just modtime, so
// you don't have to know the internals in order to make that works.
CheckIfModifiedSince(modtime time.Time) (bool, error)
// WriteNotModified sends a 304 "Not Modified" status code to the client,
// it makes sure that the content type, the content length headers
// and any "ETag" are removed before the response sent.
//
// It's mostly used internally on core/router/fs.go and context methods.
WriteNotModified()
// WriteWithExpiration like Write but it sends with an expiration datetime
// which is refreshed every package-level `StaticCacheDuration` field.
WriteWithExpiration(body []byte, modtime time.Time) (int, error)
@@ -913,6 +946,35 @@ var LimitRequestBodySize = func(maxRequestBodySizeBytes int64) Handler {
}
}
// Cache304 sends a `StatusNotModified` (304) whenever
// the "If-Modified-Since" request header (time) is before the
// time.Now() + expiresEvery (always compared to their UTC values).
// Use this `context#Cache304` instead of the "github.com/kataras/iris/cache" or iris.Cache
// for better performance.
// Clients that are compatible with the http RCF (all browsers are and tools like postman)
// will handle the caching.
// The only disadvantage of using that instead of server-side caching
// is that this method will send a 304 status code instead of 200,
// So, if you use it side by side with other micro services
// you have to check for that status code as well for a valid response.
//
// Developers are free to extend this method's behavior
// by watching system directories changes manually and use of the `ctx.WriteWithExpiration`
// with a "modtime" based on the file modified date,
// simillary to the `StaticWeb`(StaticWeb sends an OK(200) and browser disk caching instead of 304).
var Cache304 = func(expiresEvery time.Duration) Handler {
return func(ctx Context) {
now := time.Now()
if modified, err := ctx.CheckIfModifiedSince(now.Add(-expiresEvery)); !modified && err == nil {
ctx.WriteNotModified()
return
}
ctx.SetLastModified(now)
ctx.Next()
}
}
// Gzip is a middleware which enables writing
// using gzip compression, if client supports.
var Gzip = func(ctx Context) {
@@ -2092,9 +2154,9 @@ var FormatTime = func(ctx Context, t time.Time) string {
// If "modtime" is zero then it does nothing.
//
// It's mostly internally on core/router and context packages.
func SetLastModified(ctx Context, modtime time.Time) {
func (ctx *context) SetLastModified(modtime time.Time) {
if !IsZeroTime(modtime) {
ctx.Header(lastModifiedHeaderKey, FormatTime(ctx, modtime)) // or modtime.UTC()?
ctx.Header(lastModifiedHeaderKey, FormatTime(ctx, modtime.UTC())) // or modtime.UTC()?
}
}
@@ -2112,7 +2174,7 @@ func SetLastModified(ctx Context, modtime time.Time) {
// or if parsing time from the header failed.
//
// It's mostly used internally, e.g. `context#WriteWithExpiration`.
func CheckIfModifiedSince(ctx Context, modtime time.Time) (bool, error) {
func (ctx *context) CheckIfModifiedSince(modtime time.Time) (bool, error) {
if method := ctx.Method(); method != http.MethodGet && method != http.MethodHead {
return false, errors.New("skip: method")
}
@@ -2126,7 +2188,7 @@ func CheckIfModifiedSince(ctx Context, modtime time.Time) (bool, error) {
}
// sub-second precision, so
// use mtime < t+1s instead of mtime <= t to check for unmodified.
if modtime.Before(t.Add(1 * time.Second)) {
if modtime.UTC().Before(t.Add(1 * time.Second)) {
return false, nil
}
return true, nil
@@ -2137,7 +2199,7 @@ func CheckIfModifiedSince(ctx Context, modtime time.Time) (bool, error) {
// and any "ETag" are removed before the response sent.
//
// It's mostly used internally on core/router/fs.go and context methods.
func WriteNotModified(ctx Context) {
func (ctx *context) WriteNotModified() {
// RFC 7232 section 4.1:
// a sender SHOULD NOT generate representation metadata other than the
// above listed fields unless said metadata exists for the purpose of
@@ -2155,12 +2217,12 @@ func WriteNotModified(ctx Context) {
// WriteWithExpiration like Write but it sends with an expiration datetime
// which is refreshed every package-level `StaticCacheDuration` field.
func (ctx *context) WriteWithExpiration(body []byte, modtime time.Time) (int, error) {
if modified, err := CheckIfModifiedSince(ctx, modtime); !modified && err == nil {
WriteNotModified(ctx)
if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil {
ctx.WriteNotModified()
return 0, nil
}
SetLastModified(ctx, modtime)
ctx.SetLastModified(modtime)
return ctx.writer.Write(body)
}
@@ -2750,13 +2812,13 @@ const (
// You can define your own "Content-Type" header also, after this function call
// Doesn't implements resuming (by range), use ctx.SendFile instead
func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error {
if modified, err := CheckIfModifiedSince(ctx, modtime); !modified && err == nil {
WriteNotModified(ctx)
if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil {
ctx.WriteNotModified()
return nil
}
ctx.ContentType(filename)
SetLastModified(ctx, modtime)
ctx.SetLastModified(modtime)
var out io.Writer
if gzipCompression && ctx.ClientSupportsGzip() {
ctx.writer.Header().Add(varyHeaderKey, acceptEncodingHeaderKey)