Routing DSL Overview
The Akka HTTP Low-Level Server-Side API provides a Flow
- or Function
-level interface that allows
an application to respond to incoming HTTP requests by simply mapping requests to responses:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import scala.io.StdIn
object WebServer {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future map/flatmap in the end
implicit val executionContext = system.dispatcher
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(
ContentTypes.`text/html(UTF-8)`,
"<html><body>Hello world!</body></html>"))
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
HttpResponse(entity = "PONG!")
case HttpRequest(GET, Uri.Path("/crash"), _, _, _) =>
sys.error("BOOM!")
case r: HttpRequest =>
r.discardEntityBytes() // important to drain incoming HTTP Entity stream
HttpResponse(404, entity = "Unknown resource!")
}
val bindingFuture = Http().bindAndHandleSync(requestHandler, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
While it'd be perfectly possible to define a complete REST API service purely by pattern-matching against the incoming
HttpRequest
(maybe with the help of a few extractors in the way of Unfiltered) this approach becomes somewhat
unwieldy for larger services due to the amount of syntax "ceremony" required. Also, it doesn't help in keeping your
service definition as DRY as you might like.
As an alternative Akka HTTP provides a flexible DSL for expressing your service behavior as a structure of
composable elements (called Directives) in a concise and readable way. Directives are assembled into a so called
route structure which, at its top-level, forms a handler Flow
(or, alternatively, an async handler function) that
can be directly supplied to a bind
call. The conversion from Route
to flow can either be invoked explicitly
using Route.handlerFlow
or, otherwise, the conversion is also provided implicitly by
RouteResult.route2HandlerFlow
[1].
For example, the service definition from above, written using the routing DSL, would look like this:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.io.StdIn
object WebServer {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val route =
get {
pathSingleSlash {
complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"<html><body>Hello world!</body></html>"))
} ~
path("ping") {
complete("PONG!")
} ~
path("crash") {
sys.error("BOOM!")
}
}
// `route` will be implicitly converted to `Flow` using `RouteResult.route2HandlerFlow`
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
The core of the Routing DSL becomes available with a single import:
import akka.http.scaladsl.server.Directives._
This example also relies on the pre-defined support for Scala XML with:
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
The very short example shown here is certainly not the best for illustrating the savings in "ceremony" and improvements in conciseness and readability that the Routing DSL promises. The Longer Example might do a better job in this regard.
For learning how to work with the Routing DSL you should first understand the concept of Routes.
[1] | To be picked up automatically, the implicit conversion needs to be provided in the companion object of the source
type. However, as Route is just a type alias for RequestContext => Future[RouteResult] , there's no
companion object for Route . Fortunately, the implicit scope for finding an implicit conversion also
includes all types that are "associated with any part" of the source type which in this case means that the
implicit conversion will also be picked up from RouteResult.route2HandlerFlow automatically. |
Contents