Static Files
On static data and HTTP
Usually, if you fetch a file via HTTP, it ships with an ETag. An ETag specifies
a file version. So the next time the browser requests that information, it sends
the ETag along, and gets a 304 Not Modified back, so you don’t have to send the
data over the wire again.
All of these solutions are most likely slower than the equivalent in nginx or a
similar static file hoster, but they’re often fast enough.
Serving static files
Http4s provides a few helpers to handle ETags for you, they’re located in StaticFile.
import cats.effect._
// import cats.effect._
import org.http4s._
// import org.http4s._
import org.http4s.dsl.io._
// import org.http4s.dsl.io._
import java.io.File
// import java.io.File
Prerequisites
Static file support uses a blocking API, so we’ll need a blocking execution
context:
import java.util.concurrent._
import scala.concurrent.ExecutionContext
val blockingEc = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(4))
It also needs a main thread pool to shift back to. This is provided when
we’re in IOApp, but you’ll need one if you’re following along in a REPL:
implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
val routes = HttpRoutes.of[IO] {
case request @ GET -> Root / "index.html" =>
StaticFile.fromFile(new File("relative/path/to/index.html"), blockingEc, Some(request))
.getOrElseF(NotFound()) // In case the file doesn't exist
}
Serving from jars
For simple file serving, it’s possible to package resources with the jar and
deliver them from there. Append to the List
as needed.
def static(file: String, blockingEc: ExecutionContext, request: Request[IO]) =
StaticFile.fromResource("/" + file, blockingEc, Some(request)).getOrElseF(NotFound())
// static: (file: String, blockingEc: scala.concurrent.ExecutionContext, request: org.http4s.Request[cats.effect.IO])cats.effect.IO[org.http4s.Response[cats.effect.IO]]
val routes = HttpRoutes.of[IO] {
case request @ GET -> Root / path if List(".js", ".css", ".map", ".html", ".webm").exists(path.endsWith) =>
static(path, blockingEc, request)
}
// routes: org.http4s.HttpRoutes[cats.effect.IO] = Kleisli(org.http4s.HttpRoutes$$$Lambda$73414/1059513533@47780c)
Webjars
A special service exists to load files from WebJars. Add your WebJar to the
class path, as you usually would:
libraryDependencies ++= Seq(
"org.webjars" % "jquery" % "3.1.1-1"
)
// <console>:28: error: not found: value libraryDependencies
// libraryDependencies ++= Seq(
// ^
// <console>:29: error: value % is not a member of String
// "org.webjars" % "jquery" % "3.1.1-1"
// ^
Then, mount the WebjarService
like any other service:
import org.http4s.server.staticcontent.webjarService
// import org.http4s.server.staticcontent.webjarService
import org.http4s.server.staticcontent.WebjarService.{WebjarAsset, Config}
// import org.http4s.server.staticcontent.WebjarService.{WebjarAsset, Config}
// only allow js assets
def isJsAsset(asset: WebjarAsset): Boolean =
asset.asset.endsWith(".js")
// isJsAsset: (asset: org.http4s.server.staticcontent.WebjarService.WebjarAsset)Boolean
val webjars: HttpRoutes[IO] = webjarService(
Config(
filter = isJsAsset,
blockingExecutionContext = blockingEc
)
)
// webjars: org.http4s.HttpRoutes[cats.effect.IO] = Kleisli(org.http4s.server.staticcontent.WebjarService$$$Lambda$73484/1126903887@6200b8c7)
blockingEc.shutdown()