ルーティングDSLの概要

ルーティングDSLの概要

Akka HTTP 低レベルサーバーサイドAPI は、アプリケーションが受信したHTTPリクエストを簡単にレスポンスにマッピングする為の Flow 若しくは Function レベルのインターフェースを提供しています。

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

  }
}

受信した HttpRequest に対してパターンマッチを使った完璧なREST APIを ( Unfiltered の抽出子を少し使って) 完全に定義する事が可能ですが、このアプローチは大量の文法の"儀式"を必要とし、比較的大きなサービスにはいくらか扱いにくくなってしまいます。また、サービスの定義をあなたが好む DRY に保つのに役に立ちません。

Akka HTTPはその代わりとして、あなたのサービスの振る舞いを簡潔で読みやすく記述できる要素の構造( ディレクティブ と呼ばれています)として表現できる柔軟なDSLを提供しています。ディレクティブは トップレベルにおいて bind を呼ぶ事で直接供給される Flow 処理 (若しくは、非同期な処理関数)で構成されるルート構造体として組み上げられます。 Route からFlowへの変換は Route.handlerFlow を使用する事で明示的に行う事が出来ます。また、 RouteResult.route2HandlerFlow [1] による暗黙的な変換も提供されています。

例えば、ルーティングDSLを使用して記述された上記のサービスの定義は次ようになります:

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
  }
}

ルーティングDSLのコアは、一つのインポートで有効になります

import akka.http.scaladsl.server.Directives._

この例では、予め定義されたScala XMLをサポートする機能にも依存しています

import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._

ここで示されている非常に短い例は、"儀式"を抑えられる事を示すものとしても、ルーティングDSLが約束する簡潔さと可読性の改善を示すものとしても最適ではありません。 より長い例 は、この点において良い仕事をしてくれるでしょう。

ルーティングDSLがどのように動作しているかについて学ぶには、まず ルート のコンセプトを理解するべきです。

[1]

暗黙的な変換対象を自動的に拾い上げるには、型に対するコンパニオンオブジェクトを必要とします。しかし、 RouteRequestContext => Future[RouteResult] のエイリアスでしかなく、 Route のコンパニオンオブジェクトはありません。幸運にも、暗黙的な変換対象を見つける為の implicit scope には、元の型に関連する全ての型が含まれており、 RouteResult.route2HandlerFlow によって自動的に拾われ、暗黙的に変換されます。

Contents