package controllers import java.io.File import javax.inject.Inject import play.api.Logger import play.Environment import play.api.mvc.{Action, AnyContent} import play.mvc.Controller class FrontEndServingController @Inject()(private val assets: Assets, environment: Environment) extends Controller { private val logger = Logger("FrontEndServingController") 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/40994099/1011953 private var havePhysicalResourceCache = { import scala.collection.JavaConverters._ java.util.Collections.newSetFromMap(new java.util.concurrent.ConcurrentHashMap[String, java.lang.Boolean]).asScala } // https://stackoverflow.com/a/38816414/1011953 private val fileExists: (String) => Boolean = if (environment.isProd) prodFileExists else devFileExists def index: Action[AnyContent] = serve(indexFile) def frontEndPath(path: String): Action[AnyContent] = 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 = havePhysicalResourceCache.contains(path) if (!exists) { val file = new File(physicalPublicDirectory + path) exists = file.exists if (exists) havePhysicalResourceCache += path } exists } private def prodFileExists(path: String): Boolean = { var exists = havePhysicalResourceCache.contains(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) havePhysicalResourceCache += path } exists } }