browser.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package cache
  2. import (
  3. "strconv"
  4. "time"
  5. "github.com/kataras/iris/cache/client"
  6. "github.com/kataras/iris/context"
  7. )
  8. // CacheControlHeaderValue is the header value of the
  9. // "Cache-Control": "private, no-cache, max-age=0, must-revalidate, no-store, proxy-revalidate, s-maxage=0".
  10. //
  11. // It can be overridden.
  12. var CacheControlHeaderValue = "private, no-cache, max-age=0, must-revalidate, no-store, proxy-revalidate, s-maxage=0"
  13. const (
  14. // PragmaHeaderKey is the header key of "Pragma".
  15. PragmaHeaderKey = "Pragma"
  16. // PragmaNoCacheHeaderValue is the header value of "Pragma": "no-cache".
  17. PragmaNoCacheHeaderValue = "no-cache"
  18. // ExpiresHeaderKey is the header key of "Expires".
  19. ExpiresHeaderKey = "Expires"
  20. // ExpiresNeverHeaderValue is the header value of "ExpiresHeaderKey": "0".
  21. ExpiresNeverHeaderValue = "0"
  22. )
  23. // NoCache is a middleware which overrides the Cache-Control, Pragma and Expires headers
  24. // in order to disable the cache during the browser's back and forward feature.
  25. //
  26. // A good use of this middleware is on HTML routes; to refresh the page even on "back" and "forward" browser's arrow buttons.
  27. //
  28. // See `cache#StaticCache` for the opposite behavior.
  29. var NoCache = func(ctx context.Context) {
  30. ctx.Header(context.CacheControlHeaderKey, CacheControlHeaderValue)
  31. ctx.Header(PragmaHeaderKey, PragmaNoCacheHeaderValue)
  32. ctx.Header(ExpiresHeaderKey, ExpiresNeverHeaderValue)
  33. // Add the X-No-Cache header as well, for any customized case, i.e `cache#Handler` or `cache#Cache`.
  34. client.NoCache(ctx)
  35. ctx.Next()
  36. }
  37. // StaticCache middleware for caching static files by sending the "Cache-Control" and "Expires" headers to the client.
  38. // It accepts a single input parameter, the "cacheDur", a time.Duration that it's used to calculate the expiration.
  39. //
  40. // If "cacheDur" <=0 then it returns the `NoCache` middleware instaed to disable the caching between browser's "back" and "forward" actions.
  41. //
  42. // Usage: `app.Use(cache.StaticCache(24 * time.Hour))` or `app.Use(cache.Staticcache(-1))`.
  43. // A middleware, which is a simple Handler can be called inside another handler as well, example:
  44. // cacheMiddleware := cache.StaticCache(...)
  45. // func(ctx iris.Context){
  46. // cacheMiddleware(ctx)
  47. // [...]
  48. // }
  49. var StaticCache = func(cacheDur time.Duration) context.Handler {
  50. if int64(cacheDur) <= 0 {
  51. return NoCache
  52. }
  53. cacheControlHeaderValue := "public, max-age=" + strconv.Itoa(int(cacheDur.Seconds()))
  54. return func(ctx context.Context) {
  55. cacheUntil := time.Now().Add(cacheDur).Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())
  56. ctx.Header(ExpiresHeaderKey, cacheUntil)
  57. ctx.Header(context.CacheControlHeaderKey, cacheControlHeaderValue)
  58. ctx.Next()
  59. }
  60. }
  61. const ifNoneMatchHeaderKey = "If-None-Match"
  62. // ETag is another browser & server cache request-response feature.
  63. // It can be used side by side with the `StaticCache`, usually `StaticCache` middleware should go first.
  64. // This should be used on routes that serves static files only.
  65. // The key of the `ETag` is the `ctx.Request().URL.Path`, invalidation of the not modified cache method
  66. // can be made by other request handler as well.
  67. //
  68. // In typical usage, when a URL is retrieved, the web server will return the resource's current
  69. // representation along with its corresponding ETag value,
  70. // which is placed in an HTTP response header "ETag" field:
  71. //
  72. // ETag: "/mypath"
  73. //
  74. // The client may then decide to cache the representation, along with its ETag.
  75. // Later, if the client wants to retrieve the same URL resource again,
  76. // it will first determine whether the local cached version of the URL has expired
  77. // (through the Cache-Control (`StaticCache` method) and the Expire headers).
  78. // If the URL has not expired, it will retrieve the local cached resource.
  79. // If it determined that the URL has expired (is stale), then the client will contact the server
  80. // and send its previously saved copy of the ETag along with the request in a "If-None-Match" field.
  81. //
  82. // Usage with combination of `StaticCache`:
  83. // assets := app.Party("/assets", cache.StaticCache(24 * time.Hour), ETag)
  84. // assets.StaticWeb("/", "./assets") or StaticEmbedded("/", "./assets") or StaticEmbeddedGzip("/", "./assets").
  85. //
  86. // Similar to `Cache304` but it doesn't depends on any "modified date", it uses just the ETag and If-None-Match headers.
  87. //
  88. // Read more at: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching and
  89. // https://en.wikipedia.org/wiki/HTTP_ETag
  90. var ETag = func(ctx context.Context) {
  91. key := ctx.Request().URL.Path
  92. ctx.Header(context.ETagHeaderKey, key)
  93. if match := ctx.GetHeader(ifNoneMatchHeaderKey); match == key {
  94. ctx.WriteNotModified()
  95. return
  96. }
  97. ctx.Next()
  98. }
  99. // Cache304 sends a `StatusNotModified` (304) whenever
  100. // the "If-Modified-Since" request header (time) is before the
  101. // time.Now() + expiresEvery (always compared to their UTC values).
  102. // Use this `cache#Cache304` instead of the "github.com/kataras/iris/cache" or iris.Cache
  103. // for better performance.
  104. // Clients that are compatible with the http RCF (all browsers are and tools like postman)
  105. // will handle the caching.
  106. // The only disadvantage of using that instead of server-side caching
  107. // is that this method will send a 304 status code instead of 200,
  108. // So, if you use it side by side with other micro services
  109. // you have to check for that status code as well for a valid response.
  110. //
  111. // Developers are free to extend this method's behavior
  112. // by watching system directories changes manually and use of the `ctx.WriteWithExpiration`
  113. // with a "modtime" based on the file modified date,
  114. // can be used on Party's that contains a static handler,
  115. // i.e `StaticWeb`, `StaticEmbedded` or even `StaticEmbeddedGzip`.
  116. var Cache304 = func(expiresEvery time.Duration) context.Handler {
  117. return func(ctx context.Context) {
  118. now := time.Now()
  119. if modified, err := ctx.CheckIfModifiedSince(now.Add(-expiresEvery)); !modified && err == nil {
  120. ctx.WriteNotModified()
  121. return
  122. }
  123. ctx.SetLastModified(now)
  124. ctx.Next()
  125. }
  126. }