mirror of
https://github.com/kataras/iris.git
synced 2025-12-17 09:57:01 +00:00
Add one more browser (and 304 server) cache method using ETag and If-None-Match headers
And replace the 'ctx.WriteWithExpiration' with simple 'ctx.Write' at 'StaticEmbeddedHandler' of core/router/fs.go, now that we have plenty of options for client cache give the end-dev the oportunity to use them or not on static embedded handlers Former-commit-id: 9c9e2f3de3c5ad8c9e14e453b67e6b649b02bde8
This commit is contained in:
43
cache/browser.go
vendored
43
cache/browser.go
vendored
@@ -68,6 +68,46 @@ var StaticCache = func(cacheDur time.Duration) context.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
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).
|
||||
@@ -83,7 +123,8 @@ var StaticCache = func(cacheDur time.Duration) context.Handler {
|
||||
// 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 `Party#StaticWeb` (which sends status OK(200) and browser disk caching instead of 304).
|
||||
// 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()
|
||||
|
||||
25
cache/browser_test.go
vendored
25
cache/browser_test.go
vendored
@@ -76,3 +76,28 @@ func TestCache304(t *testing.T) {
|
||||
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("__")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user