Routing DSL Overview
The Akka HTTP Core Server API provides a Flow
- or Function
-level interface that allows an application to respond to incoming HTTP requests by mapping requests to responses (excerpt from Low-level server side example):
- Scala
-
source
/* * Copyright (C) 2020-2023 Lightbend Inc. <https://www.lightbend.com> */ package docs.http.scaladsl import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.Behaviors import akka.http.scaladsl.Http import akka.http.scaladsl.model.HttpMethods._ import akka.http.scaladsl.model._ import scala.concurrent.ExecutionContext import scala.io.StdIn object HttpServerLowLevel { def main(args: Array[String]): Unit = { implicit val system = ActorSystem(Behaviors.empty, "lowlevel") // needed for the future map/flatmap in the end implicit val executionContext: ExecutionContext = system.executionContext 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().newServerAt("localhost", 8080).bindSync(requestHandler) 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 } }
- Java
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, can be used to create a handler Flow
or 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.toFlow
or, otherwise, the conversion is also provided implicitly by RouteResult.routeToFlow
[1].
Here’s the complete example rewritten using the composable high-level API:
- Scala
-
source
/* * Copyright (C) 2020-2023 Lightbend Inc. <https://www.lightbend.com> */ package docs.http.scaladsl import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model.{ ContentTypes, HttpEntity } import akka.http.scaladsl.server.Directives._ import scala.io.StdIn object HttpServerHighLevel { def main(args: Array[String]): Unit = { implicit val system = ActorSystem() // needed for the future flatMap/onComplete in the end implicit val executionContext = system.dispatcher val route = get { concat( 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 an async handler val bindingFuture = Http().newServerAt("localhost", 8080).bind(route) 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 } }
- Java
The core of the Routing DSL becomes available with a single import:
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 Long 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 forRequestContext => Future[RouteResult]
, there’s no companion object forRoute
. 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 fromRouteResult.routeToFlow
automatically.