Skip to content

Commit

Permalink
Allow glob-match based cache_control
Browse files Browse the repository at this point in the history
  • Loading branch information
laurilehmijoki committed Jun 4, 2015
1 parent 9004469 commit 6acfdc1
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 3 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ Here's an example:
cache_control: public, no-transform, max-age=1200, s-maxage=1200
```

You can also specify a hash of globs, and all files matching those globs will have
the specified cache-control string:

```yaml
cache_control:
"assets/*": public, max-age=3600
"*": no-cache, no-store
```

After changing the `cache_control` setting, push with the `--force` option.
Force-pushing allows you to update the S3 object metadata of existing files.

Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/s3/website/S3.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ object S3 {
md setContentLength uploadFile.length
md setContentType contentType
upload.encodingOnS3.map(_ => "gzip") foreach md.setContentEncoding
val cacheControl: Option[String] = (upload.maxAge, config.cache_control) match {
val cacheControl: Option[String] = (upload.maxAge, upload.cacheControl) match {
case (maxAge: Some[Int], cacheCtrl: Some[String]) =>
logger.warn("Overriding the max_age setting with the cache_control setting")
cacheCtrl
Expand Down
16 changes: 15 additions & 1 deletion src/main/scala/s3/website/model/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ case class Config(
s3_endpoint: S3Endpoint,
site: Option[String],
max_age: Option[Either[Int, Map[String, Int]]],
cache_control: Option[String],
cache_control: Option[Either[String, Map[String, String]]],
gzip: Option[Either[Boolean, Seq[String]]],
gzip_zopfli: Option[Boolean],
ignore_on_server: Option[Either[String, Seq[String]]],
Expand Down Expand Up @@ -83,6 +83,20 @@ object Config {
yamlValue getOrElse Left(ErrorReport(s"The key $key has to have an int or (string -> int) value"))
}

def loadCacheControl(implicit unsafeYaml: UnsafeYaml): Either[ErrorReport, Option[Either[String, Map[String, String]]]] = {
val key = "cache_control"
val yamlValue = for {
cacheControlOption <- loadOptionalValue(key)
} yield {
Right(cacheControlOption.map {
case cacheControl if cacheControl.isInstanceOf[String] => Left(cacheControl.asInstanceOf[String])
case cacheControl if cacheControl.isInstanceOf[java.util.Map[_,_]] => Right(cacheControl.asInstanceOf[java.util.Map[String,String]].toMap) // TODO an unsafe call to asInstanceOf
})
}

yamlValue getOrElse Left(ErrorReport(s"The key $key has to have a string or (string -> string) value"))
}

def loadEndpoint(implicit unsafeYaml: UnsafeYaml): Either[ErrorReport, Option[S3Endpoint]] =
loadOptionalString("s3_endpoint").right flatMap { endpointString =>
endpointString.map(S3Endpoint.forString) match {
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/s3/website/model/Site.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ object Site {
s3_endpoint <- loadEndpoint.right
site <- loadOptionalString("site").right
max_age <- loadMaxAge.right
cache_control <- loadOptionalString("cache_control").right
cache_control <- loadCacheControl.right
gzip <- loadOptionalBooleanOrStringSeq("gzip").right
gzip_zopfli <- loadOptionalBoolean("gzip_zopfli").right
extensionless_mime_type <- loadOptionalString("extensionless_mime_type").right
Expand Down
23 changes: 23 additions & 0 deletions src/main/scala/s3/website/model/push.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,29 @@ case class Upload(originalFile: File, uploadType: UploadType)(implicit site: Sit
}
}

lazy val cacheControl: Option[String] = {
type GlobsMap = Map[String, String]
site.config.cache_control.flatMap { (intOrGlobs: Either[String, GlobsMap]) =>
type GlobsSeq = Seq[(String, String)]
def respectMostSpecific(globs: GlobsMap): GlobsSeq = globs.toSeq.sortBy(_._1.length).reverse
intOrGlobs
.right.map(respectMostSpecific)
.fold(
(cacheCtrl: String) => Some(cacheCtrl),
(globs: GlobsSeq) => {
val matchingCacheControl = (glob: String, cacheControl: String) =>
rubyRuntime.evalScriptlet(
s"""|# encoding: utf-8
|File.fnmatch('$glob', "$s3Key")""".stripMargin)
.toJava(classOf[Boolean])
.asInstanceOf[Boolean]
val fileGlobMatch = globs find Function.tupled(matchingCacheControl)
fileGlobMatch map (_._2)
}
)
}
}

/**
* May throw an exception, so remember to call this in a Try or Future monad
*/
Expand Down
50 changes: 50 additions & 0 deletions src/test/scala/s3/website/S3WebsiteSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,56 @@ class S3WebsiteSpec extends Specification {
))
logEntries must contain("[\u001B[33mwarn\u001B[0m] Overriding the max_age setting with the cache_control settin")
}

"supports all valid URI characters in the glob setting" in new BasicSetup {
config = """
|cache_control:
| "*.html": public, max-age=120
""".stripMargin
val allValidUrlCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=" // See http://stackoverflow.com/a/1547940/990356 for discussion
setLocalFile(s"$allValidUrlCharacters.html")
push()
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("public, max-age=120")
}

"be applied to files that match the glob" in new BasicSetup {
config = """
|cache_control:
| "*.html": no-store
""".stripMargin
setLocalFile("index.html")
push()
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("no-store")
}

"be applied to directories that match the glob" in new BasicSetup {
config = """
|cache_control:
| "assets/**/*.js": no-cache, no-store
""".stripMargin
setLocalFile("assets/lib/jquery.js")
push()
sentPutObjectRequest.getMetadata.getCacheControl must equalTo("no-cache, no-store")
}

"not be applied if the glob doesn't match" in new BasicSetup {
config = """
|cache_control:
| "*.js": max-age=120
""".stripMargin
setLocalFile("index.html")
push()
sentPutObjectRequest.getMetadata.getCacheControl must beNull
}

"support non-US-ASCII directory names" in new BasicSetup {
config = """
|cache_control:
| "*": no-cache
""".stripMargin
setLocalFile("tags/笔记/index.html")
push() must equalTo(0)
}
}

"cache control" can {
Expand Down

0 comments on commit 6acfdc1

Please sign in to comment.