From f2385a0408a33581cd7174f9350e5ecc79552b67 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Tue, 4 Apr 2023 12:10:03 +0800 Subject: [PATCH] Add content type inference to static resources and files (#85) --- cask/src/cask/endpoints/StaticEndpoints.scala | 22 +++++++++++++------ example/todo/app/test/src/ExampleTests.scala | 8 +++++++ example/todo/build.sc | 2 +- example/todoDb/build.sc | 2 +- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/cask/src/cask/endpoints/StaticEndpoints.scala b/cask/src/cask/endpoints/StaticEndpoints.scala index 3d9941c8ba..758579a765 100644 --- a/cask/src/cask/endpoints/StaticEndpoints.scala +++ b/cask/src/cask/endpoints/StaticEndpoints.scala @@ -3,19 +3,25 @@ package cask.endpoints import cask.router.{HttpEndpoint, Result} import cask.model.Request object StaticUtil{ - def makePath(t: String, ctx: Request) = { - (cask.internal.Util.splitPath(t) ++ ctx.remainingPathSegments) + def makePathAndContentType(t: String, ctx: Request) = { + val path = (cask.internal.Util.splitPath(t) ++ ctx.remainingPathSegments) .filter(s => s != "." && s != "..") .mkString("/") - } + val contentType = java.nio.file.Files.probeContentType(java.nio.file.Paths.get(path)) + (path, Option(contentType)) + } } + class staticFiles(val path: String, headers: Seq[(String, String)] = Nil) extends HttpEndpoint[String, Seq[String]]{ val methods = Seq("get") type InputParser[T] = QueryParamReader[T] override def subpath = true def wrapFunction(ctx: Request, delegate: Delegate) = { - delegate(Map()).map(t => cask.model.StaticFile(StaticUtil.makePath(t, ctx), headers)) + delegate(Map()).map{t => + val (path, contentTypeOpt) = StaticUtil.makePathAndContentType(t, ctx) + cask.model.StaticFile(path, headers ++ contentTypeOpt.map("Content-Type" -> _)) + } } def wrapPathSegment(s: String): Seq[String] = Seq(s) @@ -29,10 +35,12 @@ class staticResources(val path: String, type InputParser[T] = QueryParamReader[T] override def subpath = true def wrapFunction(ctx: Request, delegate: Delegate) = { - delegate(Map()).map(t => - cask.model.StaticResource(StaticUtil.makePath(t, ctx), resourceRoot, headers) - ) + delegate(Map()).map { t => + val (path, contentTypeOpt) = StaticUtil.makePathAndContentType(t, ctx) + cask.model.StaticResource(path, resourceRoot, headers ++ contentTypeOpt.map("Content-Type" -> _)) + } } + def wrapPathSegment(s: String): Seq[String] = Seq(s) } diff --git a/example/todo/app/test/src/ExampleTests.scala b/example/todo/app/test/src/ExampleTests.scala index 411974ba8c..2d088b3039 100644 --- a/example/todo/app/test/src/ExampleTests.scala +++ b/example/todo/app/test/src/ExampleTests.scala @@ -16,6 +16,14 @@ object ExampleTests extends TestSuite{ test("TodoServer") - withServer(TodoServer){ host => val page = requests.get(host).text() assert(page.contains("What needs to be done?")) + + val cssResponse = requests.get(host + "/static/index.css") + assert(cssResponse.text().contains("font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;")) + assert(cssResponse.headers("content-type") == List("text/css")) + + val jsResponse = requests.get(host + "/static/app.js") + assert(jsResponse.text().contains("initListeners()")) + assert(jsResponse.headers("content-type") == List("text/javascript")) } } diff --git a/example/todo/build.sc b/example/todo/build.sc index aa367b29ce..38025ac631 100644 --- a/example/todo/build.sc +++ b/example/todo/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends CrossScalaModule{ def ivyDeps = Agg[Dep]( - ivy"org.xerial:sqlite-jdbc:3.18.0", + ivy"org.xerial:sqlite-jdbc:3.41.2.1", ivy"io.getquill::quill-jdbc:3.4.10", ivy"com.lihaoyi::scalatags:0.9.1", ) diff --git a/example/todoDb/build.sc b/example/todoDb/build.sc index f5bf4c92dc..283206cfbd 100644 --- a/example/todoDb/build.sc +++ b/example/todoDb/build.sc @@ -4,7 +4,7 @@ import mill._, scalalib._ trait AppModule extends CrossScalaModule{ def ivyDeps = Agg[Dep]( - ivy"org.xerial:sqlite-jdbc:3.18.0", + ivy"org.xerial:sqlite-jdbc:3.41.2.1", ivy"io.getquill::quill-jdbc:3.4.10" )