In addition to theLow-Level Server-Side APIAkka HTTP provides a very flexible “Routing DSL” for elegantly defining RESTful web services. It picks up where the low-level API leaves off and offers much of the higher- level functionality of typical web servers or frameworks, like deconstruction of URIs, content negotiation or static content serving.
Note: It is recommended to read theImplications of the streaming nature of Request/Response Entities sec- tion, as it explains the underlying full-stack streaming concepts, which may be unexpected when coming from a background with non-“streaming first” HTTP Servers.
8.6.1 Routing DSL Overview
The Akka HTTPLow-Level Server-Side APIprovides aFlow- orFunction-level interface that allows an ap- plication 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
8.6. High-level Server-Side API 556
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 in- comingHttpRequest(maybe with the help of a few extractors in the way ofUnfiltered) 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 asDRYas 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 calledroute structure which, at its top-level, forms a handler Flow(or, alternatively, an async handler func- tion) that can be directly supplied to a bind call. The conversion from Route to flow can either be in- voked explicitly using Route.handlerFlow or, otherwise, the conversion is also provided implicitly by RouteResult.route2HandlerFlow1.
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]) {
1To be picked up automatically, the implicit conversion needs to be provided in the companion object of the source type. However, as Routeis just a type alias forRequestContext => Future[RouteResult], there’s no companion object forRoute. Fortunately, theimplicit scopefor 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.route2HandlerFlowautomatically.
8.6. High-level Server-Side API 557
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 im- provements in conciseness and readability that the Routing DSL promises. TheLonger Examplemight do a better job in this regard.
For learning how to work with the Routing DSL you should first understand the concept ofRoutes.
8.6.2 Routes
The “Route” is the central concept of Akka HTTP’s Routing DSL. All the structures you build with the DSL, no matter whether they consists of a single line or span several hundred lines, are instances of this type:
type Route = RequestContext ⇒ Future[RouteResult]
It’s a simple alias for a function turning aRequestContextinto aFuture[RouteResult].
Generally when a route receives a request (or rather aRequestContextfor it) it can do one of these things:
• Complete the request by returning the value ofrequestContext.complete(...)
• Reject the request by returning the value ofrequestContext.reject(...)(seeRejections)
• Fail the request by returning the value ofrequestContext.fail(...)or by just throwing an excep- tion (seeException Handling)
8.6. High-level Server-Side API 558
• Do any kind of asynchronous processing and instantly return aFuture[RouteResult]to be eventually completed later
The first case is pretty clear, by callingcompletea given response is sent to the client as reaction to the request.
In the second case “reject” means that the route does not want to handle the request. You’ll see further down in the section about route composition what this is good for.
A Route can be “sealed” using Route.seal, which relies on the in-scope RejectionHandler and ExceptionHandlerinstances to convert rejections and exceptions into appropriate HTTP responses for the client.
UsingRoute.handlerFlow or Route.asyncHandler aRoute can be lifted into a handlerFlowor async handler function to be used with abindAndHandleXXXcall from theLow-Level Server-Side API.
Note: There is also an implicit conversion fromRoutetoFlow[HttpRequest,HttpResponse,Unit]
defined in theRouteResultcompanion, which relies onRoute.handlerFlow.
RequestContext
The request context wraps anHttpRequestinstance to enrich it with additional information that are typically required by the routing logic, like anExecutionContext, Materializer,LoggingAdapterand the configuredRoutingSettings. It also contains theunmatchedPath, a value that describes how much of the request URI has not yet been matched by aPath Directive.
The RequestContextitself is immutable but contains several helper methods which allow for convenient creation of modified copies.
RouteResult
RouteResultis a simple abstract data type (ADT) that models the possible non-error results of aRoute. It is defined as such:
sealed trait RouteResult object RouteResult {
final case class Complete(response: HttpResponse) extends RouteResult final case class Rejected(rejections: immutable.Seq[Rejection]) extends
˓→RouteResult }
Usually you don’t create anyRouteResultinstances yourself, but rather rely on the pre-definedRouteDirec- tives(likecomplete,rejectorredirect) or the respective methods on theRequestContextinstead.
Composing Routes
There are three basic operations we need for building more complex routes from simpler ones:
• Route transformation, which delegates processing to another, “inner” route but in the process changes some properties of either the incoming request, the outgoing response or both
• Route filtering, which only lets requests satisfying a given filter condition pass and rejects all others
• Route chaining, which tries a second route if a given first one was rejected
The last point is achieved with the concatenation operator~, which is an extension method that becomes available when youimport akka.http.scaladsl.server.Directives._. The first two points are provided by so-calledDirectivesof which a large number is already predefined by Akka HTTP and which you can also easily create yourself.Directivesdeliver most of Akka HTTP’s power and flexibility.
8.6. High-level Server-Side API 559
The Routing Tree
Essentially, when you combine directives and custom routes via nesting and the~operator, you build a routing structure that forms a tree. When a request comes in it is injected into this tree at the root and flows down through all the branches in a depth-first manner until either some node completes it or it is fully rejected.
Consider this schematic example:
val route = a {
b { c {
... // route 1 } ~
d {
... // route 2 } ~
... // route 3 } ~
e {
... // route 4 }
}
Here five directives form a routing tree.
• Route 1 will only be reached if directivesa,bandcall let the request pass through.
• Route 2 will run ifaandbpass,crejects anddpasses.
• Route 3 will run ifaandbpass, butcanddreject.
Route 3 can therefore be seen as a “catch-all” route that only kicks in, if routes chained into preceding positions reject. This mechanism can make complex filtering logic quite easy to implement: simply put the most specific cases up front and the most general cases in the back.
8.6.3 Directives
A “Directive” is a small building block used for creating arbitrarily complexroute structures. Akka HTTP already pre-defines a large number of directives and you can easily construct your own:
Predefined Directives (alphabetically)
Directive Description
authenticateBasic Wraps the inner route with Http Basic authentication support using a givenAuthenticator[T]
authenticateBasicAsync Wraps the inner route with Http Basic authentication support using a givenAsyncAuthenticator[T]
authenticateBasicPF Wraps the inner route with Http Basic authentication support using a givenAuthenticatorPF[T]
authenticateBasicPFAsync Wraps the inner route with Http Basic authentication support using a givenAsyncAuthenticatorPF[T]
authenticateOAuth2 Wraps the inner route with OAuth Bearer Token authentication support using a givenAuthenticatorPF[T]
authenticateOAuth2Async Wraps the inner route with OAuth Bearer Token authentication support using a givenAsyncAuthenticator[T]
authenticateOAuth2PF Wraps the inner route with OAuth Bearer Token authentication support using a givenAuthenticatorPF[T]
authenticateOAuth2PFAsync Wraps the inner route with OAuth Bearer Token authentication support using a givenAsyncAuthenticatorPF[T]
authenticateOrRejectWithChallenge Lifts an authenticator function into a directive authorize Applies the given authorization check to the request
authorizeAsync Applies the given asynchronous authorization check to the request
cancelRejection Adds aTransformationRejectioncancelling all rejections equal to the given one to the rejections potentially coming back from the inner route.
cancelRejections Adds aTransformationRejectioncancelling all matching rejections to the rejections potentially coming back from the inner route checkSameOrigin Checks that the request comes from the same origin
Continued on next page
8.6. High-level Server-Side API 560
Table 8.1 – continued from previous page
Directive Description
complete Completes the request using the given arguments
completeOrRecoverWith “Unwraps” aFuture[T]and runs the inner route when the future has failed with the error as an extraction of typeThrowable completeWith Uses the marshaller for a given type to extract a completion function
conditional Wraps its inner route with support for conditional requests as defined byhttp://tools.ietf.org/html/rfc7232
cookie Extracts theHttpCookiewith the given name
decodeRequest Decompresses the request if it isgzipordeflatecompressed decodeRequestWith Decodes the incoming request using one of the given decoders
delete Rejects all non-DELETE requests
deleteCookie Adds aSet-Cookieresponse header expiring the given cookies
encodeResponse Encodes the response with the encoding that is requested by the client via theAccept-Encodingheader (NoCoding,GzipandDeflate) encodeResponseWith Encodes the response with the encoding that is requested by the client via theAccept-Encodingheader (from a user-defined set)
entity Extracts the request entity unmarshalled to a given type
extract Extracts a single value using aRequestContext ⇒ Tfunction extractDataBytes Extracts the entities data bytes as a streamSource[ByteString,Any]
extractClientIP Extracts the client’s IP from either theX-Forwarded-,Remote-AddressorX-Real-IPheader extractCredentials Extracts the potentially presentHttpCredentialsprovided with the request’sAuthorizationheader extractExecutionContext Extracts theExecutionContextfrom theRequestContext
extractMaterializer Extracts theMaterializerfrom theRequestContext extractHost Extracts the hostname part of the Host request header value
extractLog Extracts theLoggingAdapterfrom theRequestContext
extractMethod Extracts the request method
extractRequest Extracts the currentHttpRequestinstance extractRequestContext Extracts theRequestContextitself
extractRequestEntity Extracts theRequestEntityfrom theRequestContext extractScheme Extracts the URI scheme from the request
extractSettings Extracts theRoutingSettingsfrom theRequestContext extractUnmatchedPath Extracts the yet unmatched path from theRequestContext
extractUri Extracts the complete request URI
failWith Bubbles the given error up the response chain where it is dealt with by the closesthandleExceptionsdirective and itsExceptionHandler fileUpload Provides a stream of an uploaded file from a multipart request
formField Extracts an HTTP form field from the request
formFieldMap Extracts a number of HTTP form field from the request as aMap[String,String]
formFieldMultiMap Extracts a number of HTTP form field from the request as aMap[String,List[String]
formFields Extracts a number of HTTP form field from the request
formFieldSeq Extracts a number of HTTP form field from the request as aSeq[(String,String)]
get Rejects all non-GET requests
getFromBrowseableDirectories Serves the content of the given directories as a file-system browser, i.e. files are sent and directories served as browseable listings getFromBrowseableDirectory Serves the content of the given directory as a file-system browser, i.e. files are sent and directories served as browseable listings getFromDirectory Completes GET requests with the content of a file underneath a given file-system directory
getFromFile Completes GET requests with the content of a given file
getFromResource Completes GET requests with the content of a given class-path resource
getFromResourceDirectory Completes GET requests with the content of a file underneath a given “class-path resource directory”
handleExceptions Transforms exceptions thrown during evaluation of the inner route using the givenExceptionHandler handleRejections Transforms rejections produced by the inner route using the givenRejectionHandler
handleWebSocketMessages Handles websocket requests with the given handler and rejects other requests with anExpectedWebSocketRequestRejection
handleWebSocketMessagesForProtocol Handles websocket requests with the given handler if the subprotocol matches and rejects other requests with anExpectedWebSocketRequestRejectionor anUnsupportedWebSocketSubprotocolRejection.
handleWith Completes the request using a given function
head Rejects all non-HEAD requests
headerValue Extracts an HTTP header value using a givenHttpHeader ⇒ Option[T]function headerValueByName Extracts the value of the first HTTP request header with a given name
headerValueByType Extracts the first HTTP request header of the given type
headerValuePF Extracts an HTTP header value using a givenPartialFunction[HttpHeader,T]
host Rejects all requests with a non-matching host name
Continued on next page
8.6. High-level Server-Side API 561
Table 8.1 – continued from previous page
Directive Description
listDirectoryContents Completes GET requests with a unified listing of the contents of all given file-system directories logRequest Produces a log entry for every incoming request
logRequestResult Produces a log entry for every incoming request andRouteResult
logResult Produces a log entry for everyRouteResult
mapInnerRoute Transforms its innerRoutewith aRoute => Routefunction
mapRejections Transforms rejections from a previous route with animmutable.Seq[Rejection] ⇒ immutable.Seq[Rejection]function mapRequest Transforms the request with anHttpRequest => HttpRequestfunction
mapRequestContext Transforms theRequestContextwith aRequestContext => RequestContextfunction mapResponse Transforms the response with anHttpResponse => HttpResponsefunction
mapResponseEntity Transforms the response entity with anResponseEntity ⇒ ResponseEntityfunction
mapResponseHeaders Transforms the response headers with animmutable.Seq[HttpHeader] ⇒ immutable.Seq[HttpHeader]function mapRouteResult Transforms theRouteResultwith aRouteResult ⇒ RouteResultfunction
mapRouteResultFuture Transforms theRouteResultfuture with aFuture[RouteResult] ⇒ Future[RouteResult]function mapRouteResultPF Transforms theRouteResultwith aPartialFunction[RouteResult,RouteResult]
mapRouteResultWith Transforms theRouteResultwith aRouteResult ⇒ Future[RouteResult]function
mapRouteResultWithPF Transforms theRouteResultwith aPartialFunction[RouteResult,Future[RouteResult]]
mapSettings Transforms theRoutingSettingswith aRoutingSettings ⇒ RoutingSettingsfunction mapUnmatchedPath Transforms theunmatchedPathof theRequestContextusing aUri.Path ⇒ Uri.Pathfunction method Rejects all requests whose HTTP method does not match the given one
onComplete “Unwraps” aFuture[T]and runs the inner route after future completion with the future’s value as an extraction of typeTry[T]
onCompleteWithBreaker “Unwraps” aFuture[T]inside aCircuitBreakerand runs the inner route after future completion with the future’s value as an extraction of typeTry[T]
onSuccess “Unwraps” aFuture[T]and runs the inner route after future completion with the future’s value as an extraction of typeT optionalCookie Extracts theHttpCookiePairwith the given name as anOption[HttpCookiePair]
optionalHeaderValue Extracts an optional HTTP header value using a givenHttpHeader ⇒ Option[T]function optionalHeaderValueByName Extracts the value of the first optional HTTP request header with a given name
optionalHeaderValueByType Extracts the first optional HTTP request header of the given type
optionalHeaderValuePF Extracts an optional HTTP header value using a givenPartialFunction[HttpHeader,T]
options Rejects all non-OPTIONS requests
overrideMethodWithParameter Changes the request method to the value of the specified query parameter parameter Extracts a query parameter value from the request
parameterMap Extracts the request’s query parameters as aMap[String,String]
parameterMultiMap Extracts the request’s query parameters as aMap[String,List[String]]
parameters Extracts a number of query parameter values from the request
parameterSeq Extracts the request’s query parameters as aSeq[(String,String)]
pass Always simply passes the request on to its inner route, i.e. doesn’t do anything, neither with the request nor the response
patch Rejects all non-PATCH requests
path Applies the givenPathMatcherto the remaining unmatched path after consuming a leading slash pathEnd Only passes on the request to its inner route if the request path has been matched completely
pathEndOrSingleSlash Only passes on the request to its inner route if the request path has been matched completely or only consists of exactly one remaining slash pathPrefix Applies the givenPathMatcherto a prefix of the remaining unmatched path after consuming a leading slash
pathPrefixTest Checks whether the unmatchedPath has a prefix matched by the givenPathMatcherafter implicitly consuming a leading slash pathSingleSlash Only passes on the request to its inner route if the request path consists of exactly one remaining slash
pathSuffix Applies the givenPathMatcherto a suffix of the remaining unmatched path (Caution: check scaladoc!) pathSuffixTest Checks whether the unmatched path has a suffix matched by the givenPathMatcher(Caution: check scaladoc!)
post Rejects all non-POST requests
provide Injects a given value into a directive
put Rejects all non-PUT requests
rawPathPrefix Applies the given matcher directly to a prefix of the unmatched path of theRequestContext, without implicitly consuming a leading slash rawPathPrefixTest Checks whether the unmatchedPath has a prefix matched by the givenPathMatcher
recoverRejections Transforms rejections from the inner route with animmutable.Seq[Rejection] ⇒ RouteResultfunction
recoverRejectionsWith Transforms rejections from the inner route with animmutable.Seq[Rejection] ⇒ Future[RouteResult]function redirect Completes the request with redirection response of the given type to the given URI
redirectToNoTrailingSlashIfPresent If the request path ends with a slash, redirects to the same uri without trailing slash in the path
Continued on next page
8.6. High-level Server-Side API 562