Akka HTTP provides a powerful Scala DSL to create routes to handle HTTP requests. The DSL contains a set of Directive
s to extract information or manipulate requests or responses.
For example, the following cpsRoute
accepts a GET
request and extracts query parameters p1
and p2
with the help of get
and parameters
directive. If an HTTP client send a GET
request to the URL /?p1=Hello&p2=World
, it will receive the response of plain text Hello, World!
.
import akka.http.scaladsl.server._, Directives._
def cpsRoute: Route = {
get {
parameters("p1", "p2") { (p1, p2) =>
complete(s"$p1, $p2!")
}
}
}
What is unusual from PHP or other HTTP server API is that you cannot get the result of parameters
directive from the return value. Instead, you must pass a callback function to handle the result. This API style in Akka HTTP DSL is called CPS (continuation-passing style), which will cause the infamous "callback hell" problem when there are lots of directives. Since each directive requires an indentation level and a pair of curly brackets, the curly brackets can become deeply nested and the code become too complicated to read.
Alternative to Akka HTTP's continuation-passing style DSL, I recently released the library Dsl.scala-akka-http to provide direct style DSL for Akka HTTP.
Dsl.scala-akka-http
is based on Dsl.scala, which provides some compiler plugins to perform CPS-transformation. The following settings enable those compiler plugins for sbt projects.
// Common sbt settings for Dsl.scala
addCompilerPlugin("com.thoughtworks.dsl" %% "compilerplugins-bangnotation" % "1.2.0")
addCompilerPlugin("com.thoughtworks.dsl" %% "compilerplugins-reseteverywhere" % "1.2.0")
In Dsl.scala, each CPS operation can be written in direct style as a !-notation, and at compile time, the user written direct style code will be automatically translated to CPS function calls. You can check this documentation for the details of Dsl.scala
and !-notation. For now, in this Akka HTTP case, the CPS operation is TApply
, which can be added to sbt projects with the following settings:
// Sbt settings for the `TApply` keyword
libraryDependencies += "com.thoughtworks.dsl" %% "keywords-akka-http-tapply" % "1.0.0"
Then you can create Akka HTTP routes in direct style. For example, the following directStyleRoute
provides the same functionality as cpsRoute
.
import akka.http.scaladsl.server._, Directives._
import com.thoughtworks.dsl.keywords.akka.http.TApply
def directStyleRoute: Route = {
!TApply(get)
val (p1, p2) = !TApply(parameters("p1", "p2"))
complete(s"$p1, $p2!")
}
Those !TApply
are direct style operator to extract the result from directive as return values instead of callback function parameters.
Alternatively, you can use direct style DSL only for parameters
directive, and leave get
directive in CPS style, as shown in the following mixStyleRoute
:
def mixStyleRoute: Route = {
get {
val (p1, p2) = !TApply(parameters("p1", "p2"))
complete(s"$p1, $p2!")
}
}
Semantically, TApply
, along with a !-notation, can be considered a library defined keyword to perform special operations that are impossible to be implemented in an ordinary Scala library. Each keyword like TApply
can be used together with other keywords.
For example, you can use com.thoughtworks.dsl.keywords.Using
to automatically manage resources, as shown below:
// Sbt settings for the `Using` keyword
libraryDependencies += "com.thoughtworks.dsl" %% "keywords-using" % "1.2.0"
import akka.http.scaladsl.server.Directives._
import com.thoughtworks.dsl.keywords.Using
import java.nio.file.Files
import java.nio.file.Paths
import scala.collection.JavaConverters._
def currentDirectoryRoute = {
pathPrefix("current-directory") {
get {
val glob = !TApply(parameters("glob"))
val currentDirectory = !Using(Files.newDirectoryStream(Paths.get(""), glob))
path("file-names") {
complete(currentDirectory.iterator.asScala.map(_.toString).mkString(","))
} ~ path("number-of-files") {
complete(currentDirectory.iterator.asScala.size.toString)
}
}
}
}
With the help of the additional Using
keyword, the currentDirectoryRoute
will open the currentDirectory
, which is a closeable DirectoryStream
created from Files.newDirectoryStream()
, according to the glob pattern extract from the query parameter, and automatically close the directory after the HTTP request is processed completely. For example, you will find a comma separated file list of all *.md
file names of current directory at the URL /current-directory/file-names?glob=*.md
, and the URL /current-directory/number-of-files?glob=*.md
contains the number of *.md
files in current directory. The DirectoryStream
will be open and closed when processing either of the URLs.
Resource management is relatively trivial in Akka HTTP DSL, without the help of Dsl.scala, as shown below:
def akkaHttpCurrentDirectoryRoute: Route = {
parameters("glob") { glob =>
val currentDirectory = Files.newDirectoryStream(Paths.get(""), glob)
mapRouteResultFuture { future =>
future.transform { result: Try[RouteResult] =>
Try {
currentDirectory.close()
}.flatMap { _: Unit =>
result
}
}
} {
pathPrefix("current-directory") {
get {
path("file-names") {
complete(currentDirectory.iterator.asScala.map(_.toString).mkString(","))
} ~ path("number-of-files") {
complete(currentDirectory.iterator.asScala.size.toString)
}
}
}
}
}
}
You can compare akkaHttpCurrentDirectoryRoute
with currentDirectoryRoute
to learn how Dsl.scala simplifies the query parameter extraction as well as resource management.
In this article, I introduced Dsl.scala-akka-http, a library based on Dsl.scala to provide direct style DSL keyword TApply
for Akka HTTP. There are other library-defined keywords in Dsl.scala, for collection manipulation, null-safety, exception handling, parallel execution, etc. You can find them in Dsl.scala's Scaladoc.
I created the library Dsl.scala during my career in ThoughtWorks, and I will keep maintaining this library in my part time, though I have resigned from ThoughtWorks recently. Since I am back to the job market, you can contact me if you recognize my expertise in my open source contributions. I am currently seeking for career opportunity in California, especially in the San Francisco bay area or San Diego.