diff --git a/.travis.yml b/.travis.yml index b928da0d..427787be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,4 +19,9 @@ after_script: # typescript examples - cd ./typescript/_examples - go get ./... + - go test -v -cover ./... + - cd ../../ + # make sure that the _benchmarks code is working + - cd ./_benchmarks + - go get ./... - go test -v -cover ./... \ No newline at end of file diff --git a/cache/browser.go b/cache/browser.go new file mode 100644 index 00000000..6058dc83 --- /dev/null +++ b/cache/browser.go @@ -0,0 +1,139 @@ +package cache + +import ( + "strconv" + "time" + + "github.com/kataras/iris/cache/client" + "github.com/kataras/iris/context" +) + +// CacheControlHeaderValue is the header value of the +// "Cache-Control": "private, no-cache, max-age=0, must-revalidate, no-store, proxy-revalidate, s-maxage=0". +// +// It can be overriden. +var CacheControlHeaderValue = "private, no-cache, max-age=0, must-revalidate, no-store, proxy-revalidate, s-maxage=0" + +const ( + // PragmaHeaderKey is the header key of "Pragma". + PragmaHeaderKey = "Pragma" + // PragmaNoCacheHeaderValue is the header value of "Pragma": "no-cache". + PragmaNoCacheHeaderValue = "no-cache" + // ExpiresHeaderKey is the header key of "Expires". + ExpiresHeaderKey = "Expires" + // ExpiresNeverHeaderValue is the header value of "ExpiresHeaderKey": "0". + ExpiresNeverHeaderValue = "0" +) + +// NoCache is a middleware which overrides the Cache-Control, Pragma and Expires headers +// in order to disable the cache during the browser's back and forward feature. +// +// A good use of this middleware is on HTML routes; to refresh the page even on "back" and "forward" browser's arrow buttons. +// +// See `cache#StaticCache` for the opposite behavior. +var NoCache = func(ctx context.Context) { + ctx.Header(context.CacheControlHeaderKey, CacheControlHeaderValue) + ctx.Header(PragmaHeaderKey, PragmaNoCacheHeaderValue) + ctx.Header(ExpiresHeaderKey, ExpiresNeverHeaderValue) + // Add the X-No-Cache header as well, for any customized case, i.e `cache#Handler` or `cache#Cache`. + client.NoCache(ctx) + + ctx.Next() +} + +// StaticCache middleware for caching static files by sending the "Cache-Control" and "Expires" headers to the client. +// It accepts a single input parameter, the "cacheDur", a time.Duration that it's used to calculate the expiration. +// +// If "cacheDur" <=0 then it returns the `NoCache` middleware instaed to disable the caching between browser's "back" and "forward" actions. +// +// Usage: `app.Use(cache.StaticCache(24 * time.Hour))` or `app.Use(cache.Staticcache(-1))`. +// A middleware, which is a simple Handler can be called inside another handler as well, example: +// cacheMiddleware := cache.StaticCache(...) +// func(ctx iris.Context){ +// cacheMiddleware(ctx) +// [...] +// } +var StaticCache = func(cacheDur time.Duration) context.Handler { + if int64(cacheDur) <= 0 { + return NoCache + } + + cacheControlHeaderValue := "public, max-age=" + strconv.Itoa(int(cacheDur.Seconds())) + return func(ctx context.Context) { + cacheUntil := time.Now().Add(cacheDur).Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat()) + ctx.Header(ExpiresHeaderKey, cacheUntil) + ctx.Header(context.CacheControlHeaderKey, cacheControlHeaderValue) + + ctx.Next() + } +} + +const ifNoneMatchHeaderKey = "If-None-Match" + +// ETag is another browser & server cache request-response feature. +// It can be used side by side with the `StaticCache`, usually `StaticCache` middleware should go first. +// This should be used on routes that serves static files only. +// The key of the `ETag` is the `ctx.Request().URL.Path`, invalidation of the not modified cache method +// can be made by other request handler as well. +// +// In typical usage, when a URL is retrieved, the web server will return the resource's current +// representation along with its corresponding ETag value, +// which is placed in an HTTP response header "ETag" field: +// +// ETag: "/mypath" +// +// The client may then decide to cache the representation, along with its ETag. +// Later, if the client wants to retrieve the same URL resource again, +// it will first determine whether the local cached version of the URL has expired +// (through the Cache-Control (`StaticCache` method) and the Expire headers). +// If the URL has not expired, it will retrieve the local cached resource. +// If it determined that the URL has expired (is stale), then the client will contact the server +// and send its previously saved copy of the ETag along with the request in a "If-None-Match" field. +// +// Usage with combination of `StaticCache`: +// assets := app.Party("/assets", cache.StaticCache(24 * time.Hour), ETag) +// assets.StaticWeb("/", "./assets") or StaticEmbedded("/", "./assets") or StaticEmbeddedGzip("/", "./assets"). +// +// Similar to `Cache304` but it doesn't depends on any "modified date", it uses just the ETag and If-None-Match headers. +// +// Read more at: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching and +// https://en.wikipedia.org/wiki/HTTP_ETag +var ETag = func(ctx context.Context) { + key := ctx.Request().URL.Path + ctx.Header(context.ETagHeaderKey, key) + if match := ctx.GetHeader(ifNoneMatchHeaderKey); match == key { + ctx.WriteNotModified() + return + } + ctx.Next() +} + +// 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 `cache#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, +// can be used on Party's that contains a static handler, +// i.e `StaticWeb`, `StaticEmbedded` or even `StaticEmbeddedGzip`. +var Cache304 = func(expiresEvery time.Duration) context.Handler { + return func(ctx context.Context) { + now := time.Now() + if modified, err := ctx.CheckIfModifiedSince(now.Add(-expiresEvery)); !modified && err == nil { + ctx.WriteNotModified() + return + } + + ctx.SetLastModified(now) + ctx.Next() + } +} diff --git a/cache/browser_test.go b/cache/browser_test.go new file mode 100644 index 00000000..9c2867ba --- /dev/null +++ b/cache/browser_test.go @@ -0,0 +1,103 @@ +package cache_test + +import ( + "strconv" + "testing" + "time" + + "github.com/kataras/iris/cache" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + "github.com/kataras/iris/httptest" +) + +func TestNoCache(t *testing.T) { + t.Parallel() + app := iris.New() + app.Get("/", cache.NoCache, func(ctx iris.Context) { + ctx.WriteString("no_cache") + }) + + // tests + e := httptest.New(t, app) + + r := e.GET("/").Expect().Status(httptest.StatusOK) + r.Body().Equal("no_cache") + r.Header(context.CacheControlHeaderKey).Equal(cache.CacheControlHeaderValue) + r.Header(cache.PragmaHeaderKey).Equal(cache.PragmaNoCacheHeaderValue) + r.Header(cache.ExpiresHeaderKey).Equal(cache.ExpiresNeverHeaderValue) +} + +func TestStaticCache(t *testing.T) { + t.Parallel() + // test change the time format, which is not reccomended but can be done. + app := iris.New().Configure(iris.WithTimeFormat("02 Jan 2006 15:04:05 GMT")) + + cacheDur := 30 * (24 * time.Hour) + var expectedTime time.Time + app.Get("/", cache.StaticCache(cacheDur), func(ctx iris.Context) { + expectedTime = time.Now() + ctx.WriteString("static_cache") + }) + + // tests + e := httptest.New(t, app) + r := e.GET("/").Expect().Status(httptest.StatusOK) + r.Body().Equal("static_cache") + + r.Header(cache.ExpiresHeaderKey).Equal(expectedTime.Add(cacheDur).Format(app.ConfigurationReadOnly().GetTimeFormat())) + cacheControlHeaderValue := "public, max-age=" + strconv.Itoa(int(cacheDur.Seconds())) + r.Header(context.CacheControlHeaderKey).Equal(cacheControlHeaderValue) +} + +func TestCache304(t *testing.T) { + t.Parallel() + app := iris.New() + + expiresEvery := 4 * time.Second + app.Get("/", cache.Cache304(expiresEvery), func(ctx iris.Context) { + ctx.WriteString("send") + }) + // handlers + e := httptest.New(t, app) + + // when 304, content type, content length and if ETagg is there are removed from the headers. + insideCacheTimef := time.Now().Add(-expiresEvery).UTC().Format(app.ConfigurationReadOnly().GetTimeFormat()) + r := e.GET("/").WithHeader(context.IfModifiedSinceHeaderKey, insideCacheTimef).Expect().Status(httptest.StatusNotModified) + r.Headers().NotContainsKey(context.ContentTypeHeaderKey).NotContainsKey(context.ContentLengthHeaderKey).NotContainsKey("ETag") + r.Body().Equal("") + + // continue to the handler itself. + cacheInvalidatedTimef := time.Now().Add(expiresEvery).UTC().Format(app.ConfigurationReadOnly().GetTimeFormat()) // after ~5seconds. + r = e.GET("/").WithHeader(context.LastModifiedHeaderKey, cacheInvalidatedTimef).Expect().Status(httptest.StatusOK) + r.Body().Equal("send") + // now without header, it should continue to the handler itself as well. + r = e.GET("/").Expect().Status(httptest.StatusOK) + r.Body().Equal("send") +} +func TestETag(t *testing.T) { + t.Parallel() + + app := iris.New() + n := "_" + app.Get("/", cache.ETag, func(ctx iris.Context) { + ctx.WriteString(n) + n += "_" + }) + + // the first and last test writes the content with status OK without cache, + // the rest tests the cache headers and status 304 and return, so body should be "". + e := httptest.New(t, app) + + r := e.GET("/").Expect().Status(httptest.StatusOK) + r.Header("ETag").Equal("/") // test if header setted. + r.Body().Equal("_") + + e.GET("/").WithHeader("ETag", "/").WithHeader("If-None-Match", "/").Expect(). + Status(httptest.StatusNotModified).Body().Equal("") // browser is responsible, no the test engine. + + r = e.GET("/").Expect().Status(httptest.StatusOK) + r.Header("ETag").Equal("/") // test if header setted. + r.Body().Equal("__") +} diff --git a/cache/cache.go b/cache/cache.go index c6d100da..645d6313 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -65,9 +65,3 @@ func Handler(expiration time.Duration) context.Handler { h := Cache(expiration).ServeHTTP return h } - -var ( - // NoCache disables the cache for a particular request, - // can be used as a middleware or called manually from the handler. - NoCache = client.NoCache -) diff --git a/cache/cache_test.go b/cache/cache_test.go index 8c92b08f..07f30311 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/kataras/iris/cache" + "github.com/kataras/iris/cache/client" "github.com/kataras/iris/cache/client/rule" "github.com/kataras/iris" @@ -84,7 +85,7 @@ func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBody return nil } -func TestNoCache(t *testing.T) { +func TestClientNoCache(t *testing.T) { app := iris.New() var n uint32 @@ -94,7 +95,7 @@ func TestNoCache(t *testing.T) { }) app.Get("/nocache", cache.Handler(cacheDuration), func(ctx context.Context) { - cache.NoCache(ctx) // <---- + client.NoCache(ctx) // <---- atomic.AddUint32(&n, 1) ctx.Write([]byte(expectedBodyStr)) }) diff --git a/context/context.go b/context/context.go index ef1f4651..d4b47f9c 100644 --- a/context/context.go +++ b/context/context.go @@ -980,35 +980,6 @@ 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) { @@ -1602,8 +1573,6 @@ func (ctx *context) Header(name string, value string) { ctx.writer.Header().Add(name, value) } -const contentTypeHeaderKey = "Content-Type" - // ContentType sets the response writer's header key "Content-Type" to the 'cType'. func (ctx *context) ContentType(cType string) { if cType == "" { @@ -1623,13 +1592,13 @@ func (ctx *context) ContentType(cType string) { } } - ctx.writer.Header().Set(contentTypeHeaderKey, cType) + ctx.writer.Header().Set(ContentTypeHeaderKey, cType) } // GetContentType returns the response writer's header value of "Content-Type" // which may, setted before with the 'ContentType'. func (ctx *context) GetContentType() string { - return ctx.writer.Header().Get(contentTypeHeaderKey) + return ctx.writer.Header().Get(ContentTypeHeaderKey) } // StatusCode sets the status code header to the response. @@ -2198,19 +2167,31 @@ func (ctx *context) WriteString(body string) (n int, err error) { return ctx.writer.WriteString(body) } -var ( - // StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration - // which can be changed. - StaticCacheDuration = 20 * time.Second +const ( + // ContentTypeHeaderKey is the header key of "Content-Type". + ContentTypeHeaderKey = "Content-Type" - lastModifiedHeaderKey = "Last-Modified" - ifModifiedSinceHeaderKey = "If-Modified-Since" - contentDispositionHeaderKey = "Content-Disposition" - cacheControlHeaderKey = "Cache-Control" - contentEncodingHeaderKey = "Content-Encoding" - gzipHeaderValue = "gzip" - acceptEncodingHeaderKey = "Accept-Encoding" - varyHeaderKey = "Vary" + // LastModifiedHeaderKey is the header key of "Last-Modified". + LastModifiedHeaderKey = "Last-Modified" + // IfModifiedSinceHeaderKey is the header key of "If-Modified-Since". + IfModifiedSinceHeaderKey = "If-Modified-Since" + // CacheControlHeaderKey is the header key of "Cache-Control". + CacheControlHeaderKey = "Cache-Control" + // ETagHeaderKey is the header key of "ETag". + ETagHeaderKey = "ETag" + + // ContentDispositionHeaderKey is the header key of "Content-Disposition". + ContentDispositionHeaderKey = "Content-Disposition" + // ContentLengthHeaderKey is the header key of "Content-Length" + ContentLengthHeaderKey = "Content-Length" + // ContentEncodingHeaderKey is the header key of "Content-Encoding". + ContentEncodingHeaderKey = "Content-Encoding" + // GzipHeaderValue is the header value of "gzip". + GzipHeaderValue = "gzip" + // AcceptEncodingHeaderKey is the header key of "Accept-Encoding". + AcceptEncodingHeaderKey = "Accept-Encoding" + // VaryHeaderKey is the header key of "Vary". + VaryHeaderKey = "Vary" ) var unixEpochTime = time.Unix(0, 0) @@ -2251,7 +2232,7 @@ var FormatTime = func(ctx Context, t time.Time) string { // It's mostly internally on core/router and context packages. func (ctx *context) SetLastModified(modtime time.Time) { if !IsZeroTime(modtime) { - ctx.Header(lastModifiedHeaderKey, FormatTime(ctx, modtime.UTC())) // or modtime.UTC()? + ctx.Header(LastModifiedHeaderKey, FormatTime(ctx, modtime.UTC())) // or modtime.UTC()? } } @@ -2273,7 +2254,7 @@ 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") } - ims := ctx.GetHeader(ifModifiedSinceHeaderKey) + ims := ctx.GetHeader(IfModifiedSinceHeaderKey) if ims == "" || IsZeroTime(modtime) { return false, errors.New("skip: zero time") } @@ -2301,10 +2282,10 @@ func (ctx *context) WriteNotModified() { // guiding cache updates (e.g.," Last-Modified" might be useful if the // response does not have an ETag field). h := ctx.ResponseWriter().Header() - delete(h, contentTypeHeaderKey) - delete(h, contentLengthHeaderKey) - if h.Get("Etag") != "" { - delete(h, lastModifiedHeaderKey) + delete(h, ContentTypeHeaderKey) + delete(h, ContentLengthHeaderKey) + if h.Get(ETagHeaderKey) != "" { + delete(h, LastModifiedHeaderKey) } ctx.StatusCode(http.StatusNotModified) } @@ -2359,9 +2340,9 @@ func (ctx *context) StreamWriter(writer func(w io.Writer) bool) { // ClientSupportsGzip retruns true if the client supports gzip compression. func (ctx *context) ClientSupportsGzip() bool { - if h := ctx.GetHeader(acceptEncodingHeaderKey); h != "" { + if h := ctx.GetHeader(AcceptEncodingHeaderKey); h != "" { for _, v := range strings.Split(h, ";") { - if strings.Contains(v, gzipHeaderValue) { // we do Contains because sometimes browsers has the q=, we don't use it atm. || strings.Contains(v,"deflate"){ + if strings.Contains(v, GzipHeaderValue) { // we do Contains because sometimes browsers has the q=, we don't use it atm. || strings.Contains(v,"deflate"){ return true } } @@ -2896,11 +2877,6 @@ var ( errServeContent = errors.New("while trying to serve content to the client. Trace %s") ) -const ( - // contentLengthHeaderKey represents the header["Content-Length"] - contentLengthHeaderKey = "Content-Length" -) - // ServeContent serves content, headers are autoset // receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string) // @@ -2918,9 +2894,6 @@ func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime if gzipCompression && ctx.ClientSupportsGzip() { AddGzipHeaders(ctx.writer) - // ctx.writer.Header().Add(varyHeaderKey, acceptEncodingHeaderKey) - // ctx.Header(contentEncodingHeaderKey,gzipHeaderValue) - gzipWriter := acquireGzipWriter(ctx.writer) defer releaseGzipWriter(gzipWriter) out = gzipWriter @@ -2958,7 +2931,7 @@ func (ctx *context) ServeFile(filename string, gzipCompression bool) error { // // Use this instead of ServeFile to 'force-download' bigger files to the client. func (ctx *context) SendFile(filename string, destinationName string) error { - ctx.writer.Header().Set(contentDispositionHeaderKey, "attachment;filename="+destinationName) + ctx.writer.Header().Set(ContentDispositionHeaderKey, "attachment;filename="+destinationName) return ctx.ServeFile(filename, false) } @@ -3032,7 +3005,7 @@ var maxAgeExp = regexp.MustCompile(`maxage=(\d+)`) // seconds as int64 // if header not found or parse failed then it returns -1. func (ctx *context) MaxAge() int64 { - header := ctx.GetHeader(cacheControlHeaderKey) + header := ctx.GetHeader(CacheControlHeaderKey) if header == "" { return -1 } diff --git a/context/gzip_response_writer.go b/context/gzip_response_writer.go index c4f79586..a4379d9d 100644 --- a/context/gzip_response_writer.go +++ b/context/gzip_response_writer.go @@ -117,8 +117,8 @@ func (w *GzipResponseWriter) Write(contents []byte) (int, error) { func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err error) { n, err = fmt.Fprintf(w, format, a...) if err == nil { - if w.ResponseWriter.Header()[contentTypeHeaderKey] == nil { - w.ResponseWriter.Header().Set(contentTypeHeaderKey, ContentTextHeaderValue) + if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil { + w.ResponseWriter.Header().Set(ContentTypeHeaderKey, ContentTextHeaderValue) } } @@ -130,8 +130,8 @@ func (w *GzipResponseWriter) Writef(format string, a ...interface{}) (n int, err func (w *GzipResponseWriter) WriteString(s string) (n int, err error) { n, err = w.Write([]byte(s)) if err == nil { - if w.ResponseWriter.Header()[contentTypeHeaderKey] == nil { - w.ResponseWriter.Header().Set(contentTypeHeaderKey, ContentTextHeaderValue) + if w.ResponseWriter.Header()[ContentTypeHeaderKey] == nil { + w.ResponseWriter.Header().Set(ContentTypeHeaderKey, ContentTextHeaderValue) } } @@ -180,8 +180,8 @@ func (w *GzipResponseWriter) WriteNow(contents []byte) (int, error) { // AddGzipHeaders just adds the headers "Vary" to "Accept-Encoding" // and "Content-Encoding" to "gzip". func AddGzipHeaders(w ResponseWriter) { - w.Header().Add(varyHeaderKey, acceptEncodingHeaderKey) - w.Header().Add(contentEncodingHeaderKey, gzipHeaderValue) + w.Header().Add(VaryHeaderKey, AcceptEncodingHeaderKey) + w.Header().Add(ContentEncodingHeaderKey, GzipHeaderValue) } // FlushResponse validates the response headers in order to be compatible with the gzip written data diff --git a/context/transaction.go b/context/transaction.go index e0f580ac..fb0916b7 100644 --- a/context/transaction.go +++ b/context/transaction.go @@ -114,7 +114,7 @@ func (t *Transaction) Complete(err error) { reason = errWstatus.Reason } // get the content type used on this transaction - if cTypeH := t.context.ResponseWriter().Header().Get(contentTypeHeaderKey); cTypeH != "" { + if cTypeH := t.context.ResponseWriter().Header().Get(ContentTypeHeaderKey); cTypeH != "" { cType = cTypeH } diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 1b176ca5..1a21656f 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -549,23 +549,6 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro return } -// StaticCacheDuration expiration duration for INACTIVE file handlers, it's the only one global configuration -// which can be changed. -var StaticCacheDuration = 20 * time.Second - -const ( - lastModifiedHeaderKey = "Last-Modified" - ifModifiedSinceHeaderKey = "If-Modified-Since" - contentDispositionHeaderKey = "Content-Disposition" - cacheControlHeaderKey = "Cache-Control" - contentEncodingHeaderKey = "Content-Encoding" - acceptEncodingHeaderKey = "Accept-Encoding" - // contentLengthHeaderKey represents the header["Content-Length"] - contentLengthHeaderKey = "Content-Length" - contentTypeHeaderKey = "Content-Type" - varyHeaderKey = "Vary" -) - func (api *APIBuilder) registerResourceRoute(reqPath string, h context.Handler) *Route { api.Head(reqPath, h) return api.Get(reqPath, h) diff --git a/core/router/fs.go b/core/router/fs.go index 65a41332..8aa5728e 100644 --- a/core/router/fs.go +++ b/core/router/fs.go @@ -68,7 +68,7 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error names = append(names, path) } - modtime := time.Now() + // modtime := time.Now() h := func(ctx context.Context) { reqPath := strings.TrimPrefix(ctx.Request().URL.Path, "/"+vdir) @@ -100,7 +100,7 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error } ctx.ContentType(cType) - if _, err := ctx.WriteWithExpiration(buf, modtime); err != nil { + if _, err := ctx.Write(buf); err != nil { ctx.StatusCode(http.StatusInternalServerError) ctx.StopExecution() } @@ -517,8 +517,8 @@ func serveContent(ctx context.Context, name string, modtime time.Time, sizeFunc }() } ctx.Header("Accept-Ranges", "bytes") - if ctx.ResponseWriter().Header().Get(contentEncodingHeaderKey) == "" { - ctx.Header(contentLengthHeaderKey, strconv.FormatInt(sendSize, 10)) + if ctx.ResponseWriter().Header().Get(context.ContentEncodingHeaderKey) == "" { + ctx.Header(context.ContentLengthHeaderKey, strconv.FormatInt(sendSize, 10)) } } diff --git a/iris.go b/iris.go index 592f53ce..c2efb75a 100644 --- a/iris.go +++ b/iris.go @@ -375,14 +375,38 @@ var ( // 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. + // See `iris#Cache304` for an alternative, faster way. // // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/#caching Cache = cache.Handler + // NoCache is a middleware which overrides the Cache-Control, Pragma and Expires headers + // in order to disable the cache during the browser's back and forward feature. + // + // A good use of this middleware is on HTML routes; to refresh the page even on "back" and "forward" browser's arrow buttons. + // + // See `iris#StaticCache` for the opposite behavior. + // + // A shortcut of the `cache#NoCache` + NoCache = cache.NoCache + // StaticCache middleware for caching static files by sending the "Cache-Control" and "Expires" headers to the client. + // It accepts a single input parameter, the "cacheDur", a time.Duration that it's used to calculate the expiration. + // + // If "cacheDur" <=0 then it returns the `NoCache` middleware instaed to disable the caching between browser's "back" and "forward" actions. + // + // Usage: `app.Use(iris.StaticCache(24 * time.Hour))` or `app.Use(iris.Staticcache(-1))`. + // A middleware, which is a simple Handler can be called inside another handler as well, example: + // cacheMiddleware := iris.StaticCache(...) + // func(ctx iris.Context){ + // cacheMiddleware(ctx) + // [...] + // } + // + // A shortcut of the `cache#StaticCache` + StaticCache = cache.StaticCache // 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 + // Use this, which is a shortcut of the, `chache#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. @@ -394,10 +418,10 @@ var ( // 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). + // simillary to the `StaticWeb`(which sends status OK(200) and browser disk caching instead of 304). // - // A shortcut of the `context#Cache304`. - Cache304 = context.Cache304 + // A shortcut of the `cache#Cache304`. + Cache304 = cache.Cache304 ) // SPA accepts an "assetHandler" which can be the result of an