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

// import


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(
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$74195/1154140308@6642c092)


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 =
// isJsAsset: (asset: org.http4s.server.staticcontent.WebjarService.WebjarAsset)Boolean

val webjars: HttpRoutes[IO] = webjarService(
    filter = isJsAsset,
    blockingExecutionContext = blockingEc
// webjars: org.http4s.HttpRoutes[cats.effect.IO] = Kleisli(org.http4s.server.staticcontent.WebjarService$$$Lambda$74265/1144234303@8d66c2d)