Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mrubin/211f007508cd90f471d697132727808c to your computer and use it in GitHub Desktop.
Save mrubin/211f007508cd90f471d697132727808c to your computer and use it in GitHub Desktop.
Play controller for serving a React App out of /public - including front-end routes
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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment