mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 11:06:27 +01:00
Merge pull request #890 from kataras/dev
New Cache Middleware: iris.Cache304 Former-commit-id: 927ab75400aa43c3594c598b1e588defb019be2d
This commit is contained in:
commit
999fefd649
|
@ -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.
|
||||
|
||||
|
|
39
_examples/cache/client-side/main.go
vendored
Normal file
39
_examples/cache/client-side/main.go
vendored
Normal file
|
@ -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
_examples/cache/simple/main.go
vendored
5
_examples/cache/simple/main.go
vendored
|
@ -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 */
|
||||
|
|
|
@ -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")
|
||||
|
|
11
cache/cache.go
vendored
11
cache/cache.go
vendored
|
@ -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:
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
|
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())
|
||||
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
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
|
||||
}
|
||||
}
|
31
cache/entry/entry.go
vendored
31
cache/entry/entry.go
vendored
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
cache/entry/response.go
vendored
31
cache/entry/response.go
vendored
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -411,7 +411,7 @@ func detectOrWriteContentType(ctx context.Context, name string, content io.ReadS
|
|||
// content must be seeked to the beginning of the file.
|
||||
// The sizeFunc is called at most once. Its error, if any, is sent in the HTTP response.
|
||||
func serveContent(ctx context.Context, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) (string, int) /* we could use the TransactionErrResult but prefer not to create new objects for each of the errors on static file handlers*/ {
|
||||
context.SetLastModified(ctx, modtime)
|
||||
ctx.SetLastModified(modtime)
|
||||
done, rangeReq := checkPreconditions(ctx, modtime)
|
||||
if done {
|
||||
return "", http.StatusNotModified
|
||||
|
@ -651,15 +651,15 @@ func checkPreconditions(ctx context.Context, modtime time.Time) (done bool, rang
|
|||
switch checkIfNoneMatch(ctx) {
|
||||
case condFalse:
|
||||
if ctx.Method() == http.MethodGet || ctx.Method() == http.MethodHead {
|
||||
context.WriteNotModified(ctx)
|
||||
ctx.WriteNotModified()
|
||||
return true, ""
|
||||
}
|
||||
ctx.StatusCode(http.StatusPreconditionFailed)
|
||||
return true, ""
|
||||
|
||||
case condNone:
|
||||
if modified, err := context.CheckIfModifiedSince(ctx, modtime); !modified && err == nil {
|
||||
context.WriteNotModified(ctx)
|
||||
if modified, err := ctx.CheckIfModifiedSince(modtime); !modified && err == nil {
|
||||
ctx.WriteNotModified()
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
@ -785,11 +785,11 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
|||
if !showList {
|
||||
return "", http.StatusForbidden
|
||||
}
|
||||
if modified, err := context.CheckIfModifiedSince(ctx, d.ModTime()); !modified && err == nil {
|
||||
context.WriteNotModified(ctx)
|
||||
if modified, err := ctx.CheckIfModifiedSince(d.ModTime()); !modified && err == nil {
|
||||
ctx.WriteNotModified()
|
||||
return "", http.StatusNotModified
|
||||
}
|
||||
ctx.Header("Last-Modified", d.ModTime().UTC().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()))
|
||||
ctx.SetLastModified(d.ModTime())
|
||||
return dirList(ctx, f)
|
||||
}
|
||||
|
||||
|
@ -801,7 +801,7 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
|||
}
|
||||
|
||||
// else, set the last modified as "serveContent" does.
|
||||
context.SetLastModified(ctx, d.ModTime())
|
||||
ctx.SetLastModified(d.ModTime())
|
||||
|
||||
// write the file to the response writer.
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
|
@ -823,7 +823,7 @@ func serveFile(ctx context.Context, fs http.FileSystem, name string, redirect bo
|
|||
// and the binary data inside "f".
|
||||
detectOrWriteContentType(ctx, d.Name(), f)
|
||||
|
||||
return "", 200
|
||||
return "", http.StatusOK
|
||||
}
|
||||
|
||||
// toHTTPError returns a non-specific HTTP error message and status code
|
||||
|
|
23
iris.go
23
iris.go
|
@ -360,11 +360,32 @@ var (
|
|||
//
|
||||
// A shortcut for the `handlerconv#FromStd`.
|
||||
FromStd = handlerconv.FromStd
|
||||
// Cache is a middleware providing cache functionalities
|
||||
// Cache is a middleware providing server-side cache functionalities
|
||||
// to the next handlers, can be used as: `app.Get("/", iris.Cache, aboutHandler)`.
|
||||
// It should be used after Static methods.
|
||||
// See `context#Cache304` for an alternative, faster way.
|
||||
//
|
||||
// Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/#caching
|
||||
Cache = cache.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, which is a shortcut of the, `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).
|
||||
//
|
||||
// A shortcut of the `context#Cache304`.
|
||||
Cache304 = context.Cache304
|
||||
)
|
||||
|
||||
// SPA accepts an "assetHandler" which can be the result of an
|
||||
|
|
Loading…
Reference in New Issue
Block a user