Cask is a Scala HTTP micro-framework inspired by Python’s Flask. This note covers the routes, decorators, custom endpoints and websockets.

1 Getting Started

  • Define an object that inherits from cask.MainRoutes and call initialize().
  • Define routess using @cask.get or @cask.post as method annotation.
  • Define methods that take an optional input cask.Request and return cask.Response.
  • Casks provides helpers to extract and de-serialize data from requests.

Sample code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
case class MinimalRoutes()(implicit val log: cask.Logger) extends cask.Routes{
  @cask.get("/")
  def hello() = {
    "Hello World!"
  }

  @cask.post("/do-thing")
  def doThing(request: cask.Request) = {
    request.text().reverse
  }

  initialize()
}

object MinimalRoutesMain extends cask.Main{
  val allRoutes = Seq(MinimalRoutes())
}

This runs the app on http://localhost:8080. Alternatively, there is a a cask.MainRoutes can be used for simple applications.

2 Routes

Routes can be defined in multiple objects that inherit from cask.Routes. Path parameters are bound with the same name. Use param: T to match a single query parameter of type T or use param: Seq[T] to access multiple query paratmers. The path segmenat matcher uses the same name. Use Option[T] for optional parameters. Set subpath=true and use subpath: cask.Subpath to access the entire request sub-path. The name of the param doesn’t matter for subpath match.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@cask.get("/user/:userName")
def showUserProfile(userName: String) = {
  s"User $userName"
}

@cask.get("/post/:postId")
def showPost(postId: Int, param: Seq[String]) = {
  s"Post $postId $param"
}

@cask.get("/path", subpath = true)
def showSubpath(request: cask.Request) = {
  s"Subpath ${request.remainingPathSegments}"
}

Use @cask.route("path", method=Seq("get", "post", "secretmethod")) to specify multiple methods for a route.

Use @cask.postJson to process json data and @cask.postForm for form data. Use u.json.Value for raw json value or a Scala type for deserialized value, cask.FormValue for form data and cask.FormFile for uploaded file.

Use cask.Cookie to get a cookie and set the cookies attribute.

Use @cask.staticFiles to define a static file route and all subpath will be matched as static files. The annoted method returns the relative file path. Similarly, @cask.staticResources attempts to serve a request based on the JVM resource path, returning the data if a resource is present and a 404 otherwise.

Use headers to config response header. Use @cask.decorators.compress to compress the response.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@cask.staticFiles("/static/file", headers = Seq("Cache-Control" -> "max-age=31536000"))
def staticFileRoutes() = "app/resources/cask"

@cask.staticFiles("/static/css", headers = Seq("Content-Type" -> "text/css"))
  def staticFileCss() = "app/resources/css"

@cask.decorators.compress
@cask.staticResources("/static/resource")
def staticResourceRoutes() = "cask"

@cask.staticResources("/static/resource2")
def staticResourceRoutes2() = "."

Use cask.Redirect for redirects and cask.Abort for aborts.

3 Decorator and Endpoint

3.1 Decorator

A Decorator is used to annotate a function to wrap it. The trait defines two methods:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
trait Decorator[OuterReturned, InnerReturned, Input] extends scala.annotation.Annotation {
  final type InputTypeAlias = Input
  type InputParser[T] <: ArgReader[Input, T, Request]
  final type Delegate = Map[String, Input] => Result[InnerReturned]

  def wrapFunction(ctx: Request, delegate: Delegate): Result[OuterReturned]
  def getParamParser[T](implicit p: InputParser[T]) = p
}

/**
  * A [[RawDecorator]] is a decorator that operates on the raw request and
  * response stream, before and after the primary [[Endpoint]] does it's job.
  */
trait RawDecorator extends Decorator[Response.Raw, Response.Raw, Any]{
  type InputParser[T] = NoOpParser[Any, T]
}

Use wrapFunction to perform additional validation before or after the function runs. Use delegate to call the wrapped function. The wrapped function has a parameter type of Map[String, Input] and a return value type of Result[InnerReturned].

A stack of Decorators is invoked recursively with the innner-most decorator invoking the route’s EntryPoint.invoke. Usually a decorator contribues a dictionary of name-value bindings, which are eventually all passed to EntryPoint.invok. The inner decorater’s OuterReturned is the enclosing decorator’s InnerReturned.

Overriding cask.Routes#decorators to apply decorators to a set of routes defined together. Overriding cask.Main#mainDecorators to apply decorators to every endpoint.

3.2 Endpoint

An Endpoint extends a Decorator and add fields such as path and method. An HttpEndpoint is an endpoint with a Repsonse.Raw as the data type of its result.

Extending cask.Endpoint allows you to change the return type, request parameter, deserialization and add common tasks.

The predefined cask.decorators.compress can use used to gzips or deflates a response body.