ルーティング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] | 暗黙的な変換対象を自動的に拾い上げるには、型に対するコンパニオンオブジェクトを必要とします。しかし、 |
Contents