You can declare a dependency on Cask with the following using
directive:
//> using dep "com.lihaoyi::cask::0.9.2"
In your build.sbt
, you can add a dependency on Cask:
lazy val example = project.in(file("example"))
.settings(
scalaVersion := "3.4.2",
libraryDependencies += "com.lihaoyi" %% "cask" % "0.9.2",
fork := true
)
In your build.sc
, you can add a dependency on Cask:
object example extends RootModule with ScalaModule {
def scalaVersion = "3.3.3"
def ivyDeps = Agg(
ivy"com.lihaoyi::cask::0.9.2"
)
}
Serving dynamically generated content
You can create an endpoint returning dynamically generated content with the @cask.get
annotation.
import java.time.ZonedDateTime
object Example extends cask.MainRoutes {
@cask.get("/time")
def dynamic(): String = s"Current date is: ${ZonedDateTime.now()}"
initialize()
}
import java.time.ZonedDateTime
object Example extends cask.MainRoutes:
@cask.get("/time")
def dynamic(): String = s"Current date is: ${ZonedDateTime.now()}"
initialize()
The example above creates an endpoint that returns the current date and time available at /time
. The exact response will be
recreated every time you refresh the webpage.
Since the endpoint method has the String
output type, the result will be sent with the text/plain
content type.
If you want an HTML output to be interpreted by the browser, you will need to set the Content-Type
header manually
or use the Scalatags templating library, supported by Cask.
Running the example
Run the example the same way as before, assuming you use the same project structure as described in the static file tutorial.
In the terminal, the following command will start the server:
scala-cli run Example.scala
In the terminal, the following command will start the server:
sbt example/run
In the terminal, the following command will start the server:
./mill run
Access the endpoint at http://localhost:8080/time. You should see a result similar to the one below.
Current date is: 2024-07-22T09:07:05.752534+02:00[Europe/Warsaw]
Using path segments
Cask gives you the ability to access segments of the URL path within the endpoint function. Building on the example above, you can add a segment to specify that the endpoint should return the date and time in a given city.
import java.time.{ZoneId, ZonedDateTime}
object Example extends cask.MainRoutes {
private def getZoneIdForCity(city: String): Option[ZoneId] = {
import scala.jdk.CollectionConverters._
ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of)
}
@cask.get("/time/:city")
def dynamicWithCity(city: String): String = {
getZoneIdForCity(city) match {
case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}"
case None => s"Couldn't find time zone for city $city"
}
}
initialize()
}
import java.time.{ZoneId, ZonedDateTime}
object Example extends cask.MainRoutes:
private def getZoneIdForCity(city: String): Option[ZoneId] =
import scala.jdk.CollectionConverters.*
ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of)
@cask.get("/time/:city")
def dynamicWithCity(city: String): String =
getZoneIdForCity(city) match
case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}"
case None => s"Couldn't find time zone for city $city"
initialize()
In the example above, the :city
segment in /time/:city
is available through the city
argument of the endpoint method.
The name of the argument must be identical to the segment name. The getZoneIdForCity
helper method finds the timezone for
a given city, and then the current date and time are translated to that timezone.
Accessing the endpoint at http://localhost:8080/time/Paris will result in:
Current date is: 2024-07-22T09:08:33.806259+02:00[Europe/Paris]
You can use more than one path segment in an endpoint by adding more arguments to the endpoint method. It’s also possible to use paths
with an unspecified number of segments (for example /path/foo/bar/baz/
) by giving the endpoint method an argument with cask.RemainingPathSegments
type.
Consult the documentation for more details.
Using HTML templates
To create an HTML response, you can combine Cask with the Scalatags templating library.
Import the Scalatags library:
Add the Scalatags dependency in Example.sc
file:
//> using dep "com.lihaoyi::scalatags::0.13.1"
Add the Scalatags dependency in build.sbt
file:
libraryDependencies += "com.lihaoyi" %% "scalatags" % "0.13.1"
Add the Scalatags dependency in build.cs
file:
ivy"com.lihaoyi::scalatags::0.13.1"
Now the example above can be rewritten to use a template. Cask will build a response out of the doctype
automatically,
setting the Content-Type
header to text/html
.
import java.time.{ZoneId, ZonedDateTime}
import scalatags.Text.all._
object Example extends cask.MainRoutes {
private def getZoneIdForCity(city: String): Option[ZoneId] = {
import scala.jdk.CollectionConverters._
ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of)
}
@cask.get("/time/:city")
def dynamicWithCity(city: String): doctype = {
val text = getZoneIdForCity(city) match {
case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}"
case None => s"Couldn't find time zone for city $city"
}
doctype("html")(
html(
body(
p(text)
)
)
)
}
initialize()
}
import java.time.{ZoneId, ZonedDateTime}
import scalatags.Text.all.*
object Example extends cask.MainRoutes:
private def getZoneIdForCity(city: String): Option[ZoneId] =
import scala.jdk.CollectionConverters.*
ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of)
@cask.get("/time/:city")
def dynamicWithCity(city: String): doctype =
val text = getZoneIdForCity(city) match
case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}"
case None => s"Couldn't find time zone for city $city"
doctype("html")(
html(
body(
p(text)
)
)
)
initialize()
Here we get the text of the response and wrap it in a Scalatags template. Notice that the return type changed from String
to doctype
.