-
-
Save kkrs/2137dfe50d77daf20e989f378bb4550b to your computer and use it in GitHub Desktop.
StaticAssetsController
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers.staticAssets | |
import play.api.http.HeaderNames | |
import play.api.mvc.{Action, Request, Result} | |
import scala.concurrent.Future | |
case class AddNoCacheHeaders[A](action: Action[A]) extends Action[A] with HeaderNames { | |
def apply(request: Request[A]): Future[Result] = { | |
import scala.concurrent.ExecutionContext.Implicits.global | |
// some special files (i.e. index.html) cannot use "cache busting" hash characters in their filename as other React files do. We need to treat them specially and set no-cache headers on them, so that when we push a new build browsers get it without browser caching getting in the way | |
if (request.path == "/" || request.path == "/index.html" || !PhysicalResource.exists(request.path.dropWhile(_ == '/'))) { | |
action(request).map(_.withHeaders( | |
(CACHE_CONTROL -> "no-cache, no-store, must-revalidate"), | |
(PRAGMA -> "no-cache"), | |
(EXPIRES -> "0") | |
)) | |
} else if (request.path == "/service-worker.js") { | |
action(request).map(_.withHeaders( | |
(CACHE_CONTROL -> "no-cache, no-store, must-revalidate"), | |
(PRAGMA -> "no-cache"), | |
(EXPIRES -> "0") | |
)) | |
} else { | |
action(request) | |
} | |
} | |
override def parser = action.parser | |
override def executionContext = action.executionContext | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers.staticAssets | |
import java.util.concurrent.locks.ReentrantReadWriteLock | |
// A cache of what "physical" resources we know of, shared by both `FrontEndServingController` and `AddNoCacheHeaders`, an Action which wraps `FrontEndServingController`s `serve` via Action composition in order to possibly add no-cache headers to index.html | |
object PhysicalResource { | |
private val lock = new ReentrantReadWriteLock() | |
private var cache = Set[String]() | |
def exists(path: String) = { | |
lock.readLock().lock() | |
try { | |
cache.contains(path) | |
} finally { | |
lock.readLock().unlock() | |
} | |
} | |
def setExists(path: String) = { | |
lock.writeLock().lock() | |
try { | |
cache += path | |
} finally { | |
lock.writeLock().unlock() | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Technically this is not necessary because `frontEndPath("")` will fail to find an empty path in its set and will serve up index anyway. This is a slight optimization to be more explicit and just serve up index directly. | |
GET / controllers.staticAssets.StaticAssetsController.index | |
GET /$path<.*> controllers.staticAssets.StaticAssetsController.frontEndPath(path) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package controllers.staticAssets | |
import java.io.File | |
import javax.inject.Inject | |
import controllers.Assets | |
import play.Environment | |
import play.api.Logger | |
import play.api.mvc.{Action, AnyContent} | |
import play.mvc.Controller | |
class StaticAssetsController @Inject()(private val assets: Assets, | |
environment: Environment) | |
extends Controller { | |
private val logger = Logger(this.getClass.getName) | |
private val publicDirectory = "/public" | |
private val indexFile = "index.html" | |
// this is used to check for the existence of 'physical' files in dev mode | |
private val physicalPublicDirectory = s".${File.separator}public${File.separator}" | |
// this is used to check for the existence of files in a jar in prod mode | |
private val streamPublicDirectory = "public/" | |
// https://stackoverflow.com/a/38816414/1011953 | |
private val fileExists: (String) => Boolean = | |
if (environment.isProd) prodFileExists else devFileExists | |
def index: Action[AnyContent] = AddNoCacheHeaders { | |
serve(indexFile) | |
} | |
def frontEndPath(path: String): Action[AnyContent] = AddNoCacheHeaders { | |
serve(path) | |
} | |
private def serve(path: String) = { | |
if (fileExists(path)) { | |
logger.debug(s"Serving physical resource: '$path'") | |
assets.at(publicDirectory, path, aggressiveCaching = true) // use "aggressive caching" because React should generate filenames with a hash for each build | |
} else { | |
// serve up the contents of index.html without rewriting the url in the browser, so that React routes can work | |
logger.debug(s"Serving virtual resource: '$path'") | |
assets.at(publicDirectory, indexFile, aggressiveCaching = false) // don't use "aggressive caching" in case we want to update our index.html sometimes (since that filename can't change) | |
} | |
} | |
private def devFileExists(path: String): Boolean = { | |
var exists = PhysicalResource.exists(path) | |
if (!exists) { | |
val file = new File(physicalPublicDirectory + path) | |
exists = file.exists | |
if (exists) | |
PhysicalResource.setExists(path) | |
} | |
exists | |
} | |
private def prodFileExists(path: String): Boolean = { | |
var exists = PhysicalResource.exists(path) | |
if (!exists) { | |
// https://stackoverflow.com/a/43756053/1011953 | |
// https://stackoverflow.com/a/12133643/1011953 | |
val streamPath = streamPublicDirectory + path | |
val stream = getClass.getClassLoader.getResourceAsStream(streamPath) | |
exists = stream != null | |
if (exists) | |
PhysicalResource.setExists(path) | |
} | |
exists | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment