| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent afbd56a commit 9fc7afe
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -195,7 +195,6 @@ object ArticleController extends Controller with RendersItemResponse with Loggin | |||
| 195 | 195 | } | |
| 196 | 196 | ||
| 197 | 197 | def createLiveBlogModel(liveBlog: Article, response: ItemResponse, maybeRequiredBlockId: Option[String]) = { | |
| 198 | - import conf.switches.Switches.LongCacheSwitch | ||
| 199 | 198 | ||
| 200 | 199 | val pageSize = if (liveBlog.content.tags.tags.map(_.id).contains("sport/sport")) 30 else 10 | |
| 201 | 200 | val liveBlogPageModel = LiveBlogCurrentPage( | |
@@ -207,18 +206,14 @@ object ArticleController extends Controller with RendersItemResponse with Loggin | |||
| 207 | 206 | case Some(pageModel) => | |
| 208 | 207 | ||
| 209 | 208 | val cacheTime = | |
| 210 | - if (!pageModel.currentPage.isArchivePage && liveBlog.fields.isLive) liveBlog.metadata.cacheTime | ||
| 211 | - else { | ||
| 212 | - if (LongCacheSwitch.isSwitchedOn) { | ||
| 213 | - if (liveBlog.fields.lastModified > DateTime.now(liveBlog.fields.lastModified.getZone) - 1.hour) CacheTime.RecentlyUpdatedPurgable | ||
| 214 | - else if (liveBlog.fields.lastModified > DateTime.now(liveBlog.fields.lastModified.getZone) - 24.hours) CacheTime.LastDayUpdatedPurgable | ||
| 215 | - else CacheTime.NotRecentlyUpdatedPurgable | ||
| 216 | - } else { | ||
| 217 | - if (liveBlog.fields.lastModified > DateTime.now(liveBlog.fields.lastModified.getZone) - 1.hour) CacheTime.RecentlyUpdated | ||
| 218 | - else if (liveBlog.fields.lastModified > DateTime.now(liveBlog.fields.lastModified.getZone) - 24.hours) CacheTime.LastDayUpdated | ||
| 219 | - else CacheTime.NotRecentlyUpdated | ||
| 220 | - } | ||
| 221 | - } | ||
| 209 | + if (!pageModel.currentPage.isArchivePage && liveBlog.fields.isLive) | ||
| 210 | + liveBlog.metadata.cacheTime | ||
| 211 | + else if (liveBlog.fields.lastModified > DateTime.now(liveBlog.fields.lastModified.getZone) - 1.hour) | ||
| 212 | + CacheTime.RecentlyUpdated | ||
| 213 | + else if (liveBlog.fields.lastModified > DateTime.now(liveBlog.fields.lastModified.getZone) - 24.hours) | ||
| 214 | + CacheTime.LastDayUpdated | ||
| 215 | + else | ||
| 216 | + CacheTime.NotRecentlyUpdated | ||
| 222 | 217 | ||
| 223 | 218 | val liveBlogCache = liveBlog.copy( | |
| 224 | 219 | content = liveBlog.content.copy( | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -69,15 +69,6 @@ trait PerformanceSwitches { | |||
| 69 | 69 | exposeClientSide = true | |
| 70 | 70 | ) | |
| 71 | 71 | ||
| 72 | - val DoubleCacheTimesSwitch = Switch( | ||
| 73 | - SwitchGroup.Performance, | ||
| 74 | - "double-cache-times", | ||
| 75 | - "Doubles the cache time of every endpoint. Turn on to help handle exceptional load.", | ||
| 76 | - safeState = On, | ||
| 77 | - sellByDate = never, | ||
| 78 | - exposeClientSide = false | ||
| 79 | - ) | ||
| 80 | - | ||
| 81 | 72 | val RelatedContentSwitch = Switch( | |
| 82 | 73 | SwitchGroup.Performance, | |
| 83 | 74 | "related-content", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,9 +3,13 @@ package dev | |||
| 3 | 3 | import common.Assets.AssetNotFoundException | |
| 4 | 4 | import common.ExecutionContexts | |
| 5 | 5 | import java.io.File | |
| 6 | + import model.{NoCache, Cached} | ||
| 7 | + import model.Cached.WithoutRevalidationResult | ||
| 8 | + import play.api.Play | ||
| 6 | 9 | import play.api.libs.MimeTypes | |
| 7 | 10 | import play.api.mvc._ | |
| 8 | 11 | import play.api.libs.iteratee.Enumerator | |
| 12 | + import play.api.Play.current | ||
| 9 | 13 | ||
| 10 | 14 | object DevAssetsController extends Controller with ExecutionContexts { | |
| 11 | 15 | ||
@@ -42,10 +46,18 @@ object DevAssetsController extends Controller with ExecutionContexts { | |||
| 42 | 46 | if (MimeTypes.isText(mime)) s"$mime; charset=utf-8" else mime | |
| 43 | 47 | } getOrElse BINARY | |
| 44 | 48 | ||
| 45 | - Result( | ||
| 46 | - ResponseHeader(OK, Map(CONTENT_TYPE -> contentType)), | ||
| 47 | - Enumerator.fromStream(resolved.openStream()) | ||
| 48 | - ) | ||
| 49 | + val result = Result( | ||
| 50 | + ResponseHeader(OK, Map(CONTENT_TYPE -> contentType)), | ||
| 51 | + Enumerator.fromStream(resolved.openStream()) | ||
| 52 | + ) | ||
| 53 | + | ||
| 54 | + // WebDriver caches during tests. Caching CSS during tests might speed some things up. | ||
| 55 | + if (Play.isTest) { | ||
| 56 | + Cached(84000)(WithoutRevalidationResult(result)) | ||
| 57 | + } else { | ||
| 58 | + // but we don't want caching during development... | ||
| 59 | + NoCache(result) | ||
| 60 | + } | ||
| 49 | 61 | } | |
| 50 | 62 | ||
| 51 | 63 | def surveys(file: String): Action[AnyContent] = | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,28 +1,30 @@ | |||
| 1 | 1 | package model | |
| 2 | 2 | ||
| 3 | - import conf.switches.Switches | ||
| 4 | 3 | import conf.switches.Switches._ | |
| 5 | 4 | import org.joda.time.DateTime | |
| 6 | 5 | import org.scala_tools.time.Imports._ | |
| 7 | 6 | import play.api.http.Writeable | |
| 8 | 7 | import play.api.mvc._ | |
| 9 | - import play.twirl.api.Html | ||
| 8 | + import scala.math.{min, max} | ||
| 9 | + | ||
| 10 | + import scala.concurrent.ExecutionContext.Implicits.global | ||
| 10 | 11 | import scala.concurrent.Future | |
| 11 | 12 | import scala.concurrent.duration.Duration | |
| 12 | - import scala.concurrent.ExecutionContext.Implicits.global | ||
| 13 | 13 | ||
| 14 | 14 | case class CacheTime(cacheSeconds: Int) | |
| 15 | 15 | object CacheTime { | |
| 16 | 16 | ||
| 17 | - object LiveBlogActive extends CacheTime(5) | ||
| 18 | - object RecentlyUpdated extends CacheTime(10) | ||
| 19 | - object LastDayUpdated extends CacheTime(30) | ||
| 20 | - object NotRecentlyUpdated extends CacheTime(300) | ||
| 17 | + // 3800 seems slightly arbitrary, but our CDN caches to disk if above 3700 | ||
| 18 | + // https://community.fastly.com/t/why-isnt-serve-stale-working-as-expected/369 | ||
| 19 | + private def extended(cacheTime: Int) = if (LongCacheSwitch.isSwitchedOn) 3800 else cacheTime | ||
| 20 | + | ||
| 21 | 21 | object Default extends CacheTime(60) | |
| 22 | - object RecentlyUpdatedPurgable extends CacheTime(300) | ||
| 23 | - object LastDayUpdatedPurgable extends CacheTime(1200) | ||
| 24 | - object NotRecentlyUpdatedPurgable extends CacheTime(1800) | ||
| 22 | + object LiveBlogActive extends CacheTime(5) | ||
| 23 | + object RecentlyUpdated extends CacheTime(60) | ||
| 25 | 24 | ||
| 25 | + def LastDayUpdated = CacheTime(extended(60)) | ||
| 26 | + def NotRecentlyUpdated = CacheTime(extended(300)) | ||
| 27 | + def NotRecentlyUpdatedPurgable = CacheTime(extended(1800)) | ||
| 26 | 28 | } | |
| 27 | 29 | ||
| 28 | 30 | object Cached extends implicits.Dates { | |
@@ -80,19 +82,32 @@ object Cached extends implicits.Dates { | |||
| 80 | 82 | cacheableResult.result | |
| 81 | 83 | } | |
| 82 | 84 | ||
| 83 | - private def cacheHeaders(seconds: Int, result: Result, maybeHash: Option[(Hash, Option[String])]) = { | ||
| 85 | + /* | ||
| 86 | + NOTE, if you change these headers make sure they are compatible with our Edge Cache | ||
| 87 | + | ||
| 88 | + see | ||
| 89 | + http://tools.ietf.org/html/rfc5861 | ||
| 90 | + http://www.fastly.com/blog/stale-while-revalidate | ||
| 91 | + http://docs.fastly.com/guides/22966608/40347813 | ||
| 92 | + | ||
| 93 | + This explains Surrogate-Control vs Cache-Control | ||
| 94 | + TLDR Surrogate-Control is used by the CDN, Cache-Control by the browser - do *not* add `private` to Cache-Control | ||
| 95 | + https://docs.fastly.com/guides/tutorials/cache-control-tutorial | ||
| 96 | + */ | ||
| 97 | + private def cacheHeaders(maxAge: Int, result: Result, maybeHash: Option[(Hash, Option[String])]) = { | ||
| 84 | 98 | val now = DateTime.now | |
| 85 | - val expiresTime = now + seconds.seconds | ||
| 86 | - val maxAge = if (DoubleCacheTimesSwitch.isSwitchedOn) seconds * 2 else seconds | ||
| 99 | + val expiresTime = if (LongCacheSwitch.isSwitchedOn) now + min(maxAge, 60).seconds else now + maxAge.seconds | ||
| 87 | 100 | ||
| 88 | - // NOTE, if you change these headers make sure they are compatible with our Edge Cache | ||
| 101 | + val staleWhileRevalidateSeconds = max(maxAge / 10, 1) | ||
| 102 | + val surrogateCacheControl = s"max-age=$maxAge, stale-while-revalidate=$staleWhileRevalidateSeconds, stale-if-error=$tenDaysInSeconds" | ||
| 89 | 103 | ||
| 90 | - // see | ||
| 91 | - // http://tools.ietf.org/html/rfc5861 | ||
| 92 | - // http://www.fastly.com/blog/stale-while-revalidate | ||
| 93 | - // http://docs.fastly.com/guides/22966608/40347813 | ||
| 94 | - val staleWhileRevalidateSeconds = math.max(maxAge / 10, 1) | ||
| 95 | - val cacheControl = s"max-age=$maxAge, stale-while-revalidate=$staleWhileRevalidateSeconds, stale-if-error=$tenDaysInSeconds" | ||
| 104 | + val cacheControl = if (LongCacheSwitch.isSwitchedOn) { | ||
| 105 | + val browserMaxAge = min(maxAge, 60) | ||
| 106 | + val browserStaleWhileRevalidateSeconds = max(browserMaxAge / 10, 1) | ||
| 107 | + s"max-age=$browserMaxAge, stale-while-revalidate=$browserStaleWhileRevalidateSeconds, stale-if-error=$tenDaysInSeconds" | ||
| 108 | + } else { | ||
| 109 | + surrogateCacheControl | ||
| 110 | + } | ||
| 96 | 111 | ||
| 97 | 112 | val (etagHeaderString, validatedResult): (String, Result) = maybeHash.map { case (hash, maybeHashToMatch) => | |
| 98 | 113 | val etag = s"""W/"hash${hash.string}"""" | |
@@ -106,13 +121,15 @@ object Cached extends implicits.Dates { | |||
| 106 | 121 | ) | |
| 107 | 122 | ||
| 108 | 123 | validatedResult.withHeaders( | |
| 109 | - "Surrogate-Control" -> cacheControl, | ||
| 124 | + | ||
| 125 | + // the cache headers used by the CDN | ||
| 126 | + "Surrogate-Control" -> surrogateCacheControl, | ||
| 127 | + // the cache headers that make their way through to the browser | ||
| 110 | 128 | "Cache-Control" -> cacheControl, | |
| 129 | + | ||
| 111 | 130 | "Expires" -> expiresTime.toHttpDateTimeString, | |
| 112 | 131 | "Date" -> now.toHttpDateTimeString, | |
| 113 | 132 | "ETag" -> etagHeaderString) | |
| 114 | - | ||
| 115 | - | ||
| 116 | 133 | } | |
| 117 | 134 | } | |
| 118 | 135 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -389,13 +389,6 @@ object Article { | |||
| 389 | 389 | analyticsName = s"GFE:$section:$contentType:${id.substring(id.lastIndexOf("/") + 1)}", | |
| 390 | 390 | adUnitSuffix = section + "/" + contentType.toLowerCase, | |
| 391 | 391 | schemaType = Some(ArticleSchemas(content.tags)), | |
| 392 | - cacheTime = if (!fields.isLive && LongCacheSwitch.isSwitchedOn) { | ||
| 393 | - if (fields.lastModified > DateTime.now(fields.lastModified.getZone) - 1.hour) CacheTime.RecentlyUpdatedPurgable | ||
| 394 | - else if (fields.lastModified > DateTime.now(fields.lastModified.getZone) - 24.hours) CacheTime.LastDayUpdatedPurgable | ||
| 395 | - else CacheTime.NotRecentlyUpdatedPurgable | ||
| 396 | - } else { | ||
| 397 | - content.metadata.cacheTime | ||
| 398 | - }, | ||
| 399 | 392 | iosType = Some("Article"), | |
| 400 | 393 | javascriptConfigOverrides = javascriptConfig, | |
| 401 | 394 | opengraphPropertiesOverrides = opengraphProperties, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,8 +2,7 @@ package model | |||
| 2 | 2 | ||
| 3 | 3 | import com.gu.contentapi.client.model.v1.{Content => ApiContent, ContentFields} | |
| 4 | 4 | import com.gu.contentapi.client.utils.CapiModelEnrichment.RichJodaDateTime | |
| 5 | - import conf.switches.Switches | ||
| 6 | - import conf.switches.Switches.DoubleCacheTimesSwitch | ||
| 5 | + import conf.switches.Switches.LongCacheSwitch | ||
| 7 | 6 | import model.Cached.{WithoutRevalidationResult, RevalidatableResult} | |
| 8 | 7 | import org.joda.time.DateTime | |
| 9 | 8 | import org.scala_tools.time.Imports._ | |
@@ -13,7 +12,8 @@ import play.api.mvc.Results | |||
| 13 | 12 | class CachedTest extends FlatSpec with Matchers with Results with implicits.Dates { | |
| 14 | 13 | ||
| 15 | 14 | "Cached" should "cache live content for 5 seconds" in { | |
| 16 | - Switches.DoubleCacheTimesSwitch.switchOff() | ||
| 15 | + LongCacheSwitch.switchOff() | ||
| 16 | + | ||
| 17 | 17 | ||
| 18 | 18 | val modified = new DateTime(2001, 5, 20, 12, 3, 4, 555) | |
| 19 | 19 | val liveContent = content(lastModified = modified, live = true) | |
@@ -26,22 +26,22 @@ class CachedTest extends FlatSpec with Matchers with Results with implicits.Date | |||
| 26 | 26 | headers("Cache-Control") should be("max-age=5, stale-while-revalidate=1, stale-if-error=864000") | |
| 27 | 27 | } | |
| 28 | 28 | ||
| 29 | - it should "cache content less than 1 hour old for 10 seconds" in { | ||
| 30 | - Switches.DoubleCacheTimesSwitch.switchOff() | ||
| 29 | + it should "cache content less than 1 hour old for 60 seconds" in { | ||
| 30 | + LongCacheSwitch.switchOff() | ||
| 31 | 31 | ||
| 32 | 32 | val modifiedAlmost1HourAgo = DateTime.now - 58.minutes | |
| 33 | 33 | val liveContent = content(lastModified = modifiedAlmost1HourAgo, live = false) | |
| 34 | 34 | ||
| 35 | - liveContent.metadata.cacheTime.cacheSeconds should be(10) | ||
| 35 | + liveContent.metadata.cacheTime.cacheSeconds should be(60) | ||
| 36 | 36 | ||
| 37 | - val result = Cached(10, WithoutRevalidationResult(Ok("foo")), None) | ||
| 37 | + val result = Cached(60, WithoutRevalidationResult(Ok("foo")), None) | ||
| 38 | 38 | val headers = result.header.headers | |
| 39 | 39 | ||
| 40 | - headers("Cache-Control") should be("max-age=10, stale-while-revalidate=1, stale-if-error=864000") | ||
| 40 | + headers("Cache-Control") should be("max-age=60, stale-while-revalidate=6, stale-if-error=864000") | ||
| 41 | 41 | } | |
| 42 | 42 | ||
| 43 | 43 | it should "cache older content for 5 minutes" in { | |
| 44 | - Switches.DoubleCacheTimesSwitch.switchOff() | ||
| 44 | + LongCacheSwitch.switchOff() | ||
| 45 | 45 | ||
| 46 | 46 | val modifiedLongAgo = DateTime.now - 25.hours | |
| 47 | 47 | val liveContent = content(lastModified = modifiedLongAgo, live = false) | |
@@ -55,7 +55,7 @@ class CachedTest extends FlatSpec with Matchers with Results with implicits.Date | |||
| 55 | 55 | } | |
| 56 | 56 | ||
| 57 | 57 | it should "cache other things for 1 minute" in { | |
| 58 | - Switches.DoubleCacheTimesSwitch.switchOff() | ||
| 58 | + LongCacheSwitch.switchOff() | ||
| 59 | 59 | ||
| 60 | 60 | val page = SimplePage(MetaData.make( | |
| 61 | 61 | id = "", | |
@@ -71,18 +71,8 @@ class CachedTest extends FlatSpec with Matchers with Results with implicits.Date | |||
| 71 | 71 | headers("Cache-Control") should be("max-age=60, stale-while-revalidate=6, stale-if-error=864000") | |
| 72 | 72 | } | |
| 73 | 73 | ||
| 74 | - it should "double the cache time if DoubleCacheTimesSwitch is switched on" in { | ||
| 75 | - | ||
| 76 | - DoubleCacheTimesSwitch.switchOn() | ||
| 77 | - | ||
| 78 | - val result = Cached(10, WithoutRevalidationResult(Ok("foo")), None) | ||
| 79 | - val headers = result.header.headers | ||
| 80 | - | ||
| 81 | - headers("Cache-Control") should be("max-age=20, stale-while-revalidate=2, stale-if-error=864000") | ||
| 82 | - } | ||
| 83 | - | ||
| 84 | 74 | it should "have at least 1 second stale-while-revalidate" in { | |
| 85 | - DoubleCacheTimesSwitch.switchOff() | ||
| 75 | + LongCacheSwitch.switchOff() | ||
| 86 | 76 | ||
| 87 | 77 | val result = Cached(5, WithoutRevalidationResult(Ok("foo")), None) | |
| 88 | 78 | val headers = result.header.headers | |
@@ -91,7 +81,7 @@ class CachedTest extends FlatSpec with Matchers with Results with implicits.Date | |||
| 91 | 81 | } | |
| 92 | 82 | ||
| 93 | 83 | it should "set Surrogate-Control the same as Cache-Control" in { | |
| 94 | - Switches.DoubleCacheTimesSwitch.switchOff() | ||
| 84 | + LongCacheSwitch.switchOff() | ||
| 95 | 85 | ||
| 96 | 86 | val result = Cached(60, WithoutRevalidationResult(Ok("foo")), None) | |
| 97 | 87 | val headers = result.header.headers | |
@@ -100,8 +90,35 @@ class CachedTest extends FlatSpec with Matchers with Results with implicits.Date | |||
| 100 | 90 | headers("Cache-Control") should equal (headers("Surrogate-Control")) | |
| 101 | 91 | } | |
| 102 | 92 | ||
| 103 | - it should "etag should be added" in { | ||
| 104 | - DoubleCacheTimesSwitch.switchOff() | ||
| 93 | + "Longer cache control" should "be applied to SurrogateControl if enabled" in { | ||
| 94 | + LongCacheSwitch.switchOn() | ||
| 95 | + | ||
| 96 | + val modifiedLongAgo = DateTime.now - 25.hours | ||
| 97 | + val liveContent = content(lastModified = modifiedLongAgo, live = false) | ||
| 98 | + | ||
| 99 | + liveContent.metadata.cacheTime.cacheSeconds should be(3800) | ||
| 100 | + | ||
| 101 | + val result = Cached(3800, WithoutRevalidationResult(Ok("foo")), None) | ||
| 102 | + val headers = result.header.headers | ||
| 103 | + | ||
| 104 | + headers("Surrogate-Control") should be("max-age=3800, stale-while-revalidate=380, stale-if-error=864000") | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + it should "limit the max-age to 60" in { | ||
| 108 | + LongCacheSwitch.switchOn() | ||
| 109 | + | ||
| 110 | + val modifiedLongAgo = DateTime.now - 25.hours | ||
| 111 | + val liveContent = content(lastModified = modifiedLongAgo, live = false) | ||
| 112 | + | ||
| 113 | + liveContent.metadata.cacheTime.cacheSeconds should be(3800) | ||
| 114 | + | ||
| 115 | + val result = Cached(3800, WithoutRevalidationResult(Ok("foo")), None) | ||
| 116 | + val headers = result.header.headers | ||
| 117 | + | ||
| 118 | + headers("Cache-Control") should be("max-age=60, stale-while-revalidate=6, stale-if-error=864000") | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + "ETags" should "should be added" in { | ||
| 105 | 122 | ||
| 106 | 123 | val result = Cached(5, RevalidatableResult(Ok("foo"), "A"), None) | |
| 107 | 124 | val headers = result.header.headers | |
@@ -112,7 +129,7 @@ class CachedTest extends FlatSpec with Matchers with Results with implicits.Date | |||
| 112 | 129 | } | |
| 113 | 130 | ||
| 114 | 131 | it should "wrong etag should be ignored" in { | |
| 115 | - DoubleCacheTimesSwitch.switchOff() | ||
| 132 | + LongCacheSwitch.switchOff() | ||
| 116 | 133 | ||
| 117 | 134 | val result = Cached(5, RevalidatableResult(Ok("foo"), "A"), Some("""W/"hasheroo"""")) | |
| 118 | 135 | val headers = result.header.headers | |
@@ -123,7 +140,7 @@ class CachedTest extends FlatSpec with Matchers with Results with implicits.Date | |||
| 123 | 140 | } | |
| 124 | 141 | ||
| 125 | 142 | it should "correct etag should not be ignored" in { | |
| 126 | - DoubleCacheTimesSwitch.switchOff() | ||
| 143 | + LongCacheSwitch.switchOff() | ||
| 127 | 144 | ||
| 128 | 145 | val result = Cached(5, RevalidatableResult(Ok("foo"), "A"), Some("""W/"hash96"""")) | |
| 129 | 146 | val headers = result.header.headers | |
| Back | FazBrowse Home | New Git URL |
0 commit comments