イントロダクション
Akka HTTPモジュールは完全なサーバーを実装します - そして、akka-actor と akka-stream の頂点にあるクライアントサイドHTTPスタックです。これはwebフレームワークというよりもむしろより汎用的なツールキットで、HTTPベースのサービスの提供と利用を行います。もちろんブラウザとのインタラクションもスコープには含まれていますが、Akka HTTPの主要な焦点ではありません。
Akka HTTPはオープンに設計されていて、多くの場合で"同じことをする"ためのいくつかの異なったAPIレベルを提供します。あなたはアプリケーションに最も適した抽象化のAPIレベルを選択することができます。これが意味するのは、もし高レベルのAPIを使うことで何かを達成する際に問題が生じたら、より柔軟ですがより多くのアプリケーションコードを書かなければならない、低レベルのAPIを使って対処する、よい機会であるということです。
哲学
Akka HTTPは アプリケーションのコアというよりも、統合レイヤーを構築するためのツールを提供することに明確な重点をおいています。フレームワークというよりも、ライブラリのスイートとして考えられています。
フレームワークという用語を考えることは好まれることですが、それはアプリケーションを構築する際の枠組みを与えるものです。多くの決定はすでになされていて、すぐに始められて結果をすばやく届けられるサポートの構成を含む基礎を提供します。ある意味で、フレームワークは骨格であり、生命を与えるためにアプリケーションという肉付けを行うものです。このようなフレームワークは、アプリケーション開発を開始する以前に選択できて、フレームワークの「物事のやり方」に沿うようにするならば最高の働きをします。
例えば、もしブラウザで見えるwebアプリケーションを構築しているなら、webフレームワークを選んでその上にアプリケーションを構築することには意味があります。なぜなら、アプリケーションの「コア」はブラウザとwebサーバーのコードとの相互作用であるからです。フレームワークの作者はこのようなアプリケーションを設計する「実績のある」方法のひとつを選んでいて、アプリケーションテンプレートの空白を埋めさせます。このようなベストプラクティスのアーキテクチャに頼れることは、物事をすばやく行うための偉大な財産です。
しかしながら、アプリケーションのコアがブラウザとの相互作用ではなく、何かに特化した複雑と思われるビジネスサービスであるために、およそwebアプリケーションであるとは言えず、なおかつ外界との接続にREST/HTTPをめったに使わないのであれば、webフレームワークはあなたの必要とするものではないでしょう。この場合、アプリケーションアーキテクチャはインターフェースではなく、コアにとって意味のあるもので記述されるべきです。その上、ビューテンプレート、アセット管理、JavaScriptとCSSの生成/操作/最小化、国際化サポート、AJAXサポート、といった、存在するであろうブラウザに特化したフレームワークコンポーネントからは何の利益も得られないでしょう。
Akka HTTPは「フレームワークではない」ように特別に設計されていますが、それは私たちがフレームワークを嫌っているのではなくて、ユースケース上フレームワークが正しい選択でないためです。Akka HTTPはHTTPベースの統合レイヤーの構築のためにあり、傍観者であろうとします。したがって、Akka HTTPの「上に」アプリケーションを構築することは通常なく、何か意味のあるものの上にアプリケーションを構築します。Akka HTTPはHTTPインテグレーションの要求に対してはめったに使われません。
Akka HTTPの使用
Akka HTTPは別のjarファイルとして提供されています。使用する際は以下の依存関係をインクルードしていることを確認してください。
"com.typesafe.akka" %% "akka-http-experimental" % "@version@" @crossString@
akka-http
は2つのモジュール akka-http-experimental
と akka-http-core
からなることを心に留めておいてください。 akka-http-experimental
は akka-http-core
に依存しているので、後者を明示的に持ってくる必要はありません。もっぱら低レベルAPIに頼る場合ではまだこのようにする必要があります。
HTTPサーバーのルーティングDSL
Akkaの高レベルのルーティングAPIは、HTTPの「経路」を記述し、それらがどのように扱われるのかを記述するDSLを提供します。各経路は、特定の1種類のリクエストに絞り込む、1つまたは複数のレベルの Directive
からなります。
例えば、ある経路がリクエストの path
とマッチすることにより開始するかもしれないとき、これがもし「/hello」にしかマッチしないとしたら、それはHTTP get
リクエストのみに絞り込まれて、HTTP OKとともにレスポンスボディの文字列として返された文字列リテラルと共に complete
します。
通信のフォーマットとオブジェクトの間でのリクエストとレスポンスの本体の変換は、ルーティングの定義とは切り離されていて、「磁石の」パターンにより暗黙に引き寄せられたマーシャラーにより行われます。これはスコープ中で暗黙のマーシャラーが有効である限り、どのような種類のオブジェクトによるリクエストであっても complete
することができることを意味します。
デフォルトのマーシャラーはStringやByteStringのような他純なオブジェクトとして提供されますし、例えばJSONのようにあなた自身で定義することもできます。追加のモジュールはspray-jsonライブラリを使ってJSONシリアライゼーションを提供します(詳細は JSON Support を参照してください)。
Route DSLを使って作られた Route
はHTTPリクエストを受けるためにポートに「バインドされます」:
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
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("my-system")
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val route =
path("hello") {
get {
complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
}
}
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
}
}
よくあるユースケースは、JSONへのマーシャラー変換を持つモデルオブジェクトを使ってリクエストに応答するものです。この場合、2つの別々のルーティングが見られます。最初のルーティングはデータベースへの非同期クエリを発行し、JSONレスポンスの中に Future[Option[Item]]
をマーシャリングします。2つめはデータベースを保存して完了したときにOKを返すようなリクエストを受信し、 Order
をアンマーシャリングします。
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.Done
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
import scala.io.StdIn
import scala.concurrent.Future
object WebServer {
// domain model
final case class Item(name: String, id: Long)
final case class Order(items: List[Item])
// formats for unmarshalling and marshalling
implicit val itemFormat = jsonFormat2(Item)
implicit val orderFormat = jsonFormat1(Order)
// (fake) async database query api
def fetchItem(itemId: Long): Future[Option[Item]] = ???
def saveOrder(order: Order): Future[Done] = ???
def main(args: Array[String]) {
// needed to run the route
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future map/flatmap in the end
implicit val executionContext = system.dispatcher
val route: Route =
get {
pathPrefix("item" / LongNumber) { id =>
// there might be no item for a given id
val maybeItem: Future[Option[Item]] = fetchItem(id)
onSuccess(maybeItem) {
case Some(item) => complete(item)
case None => complete(StatusCodes.NotFound)
}
}
} ~
post {
path("create-order") {
entity(as[Order]) { order =>
val saved: Future[Done] = saveOrder(order)
onComplete(saved) { done =>
complete("order created")
}
}
}
}
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
}
}
この例でのマーシャリングとアンマーシャリングのロジックはspray-jsonライブラリによって提供されています(使い方の詳細はこちら: JSON Support )。
Akka HTTPの強力さのひとつは、ストリーミングデータがその心臓部にあるということです。これが意味するのは、非常に巨大なリクエストやレスポンスであっても、サーバーが一定のメモリ使用量にしか達しないように、リクエストとレスポンスの本体がともに流れていくことができるということです。ストリーミングのレスポンスはリモートクライアントによりバックプレッシャーされます。サーバーはクライアントが扱えるよりもデータをすばやく送信することはできないためです。リクエストのストリーミングとは、サーバーがリモートクライアントに対してどの程度すばやくリクエスト本体のデータを送信するかを決定することを意味しています。
クライアントが受け入れるまでランダムな数をストリームする例:
import akka.actor.ActorSystem
import akka.stream.scaladsl._
import akka.util.ByteString
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{HttpEntity, ContentTypes}
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.util.Random
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
// streams are re-usable so we can define it here
// and use it for every request
val numbers = Source.fromIterator(() =>
Iterator.continually(Random.nextInt()))
val route =
path("random") {
get {
complete(
HttpEntity(
ContentTypes.`text/plain(UTF-8)`,
// transform each number to a chunk of bytes
numbers.map(n => ByteString(s"$n\n"))
)
)
}
}
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
}
}
低速のHTTPクライアントでこのサービスに接続すると、サーバーで一定のメモリ使用量により次の乱数が必要に応じて生成されるようにバックプレッシャーがかかります。これはcurlを使って転送レートを制限すると見ることができます: curl --limit-rate 50b 127.0.0.1:8080/random
Akka HTTPルーティングはアクターと簡単にやりとりします。 この例では、1つのルートではfire and forgetスタイルで入札を行うことを可能とし、2番目のルートにはアクターとのリクエスト/レスポンスインタラクションが含まれています。結果のレスポンスはjsonとしてレンダリングされ、レスポンスがアクターから到着したときに返されます。
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.pattern.ask
import akka.stream.ActorMaterializer
import akka.util.Timeout
import spray.json.DefaultJsonProtocol._
import scala.concurrent.duration._
import scala.io.StdIn
object WebServer {
case class Bid(userId: String, bid: Int)
case object GetBids
case class Bids(bids: List[Bid])
// these are from spray-json
implicit val bidFormat = jsonFormat2(Bid)
implicit val bidsFormat = jsonFormat1(Bids)
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 auction = system.actorOf(Auction.props, "auction")
val route =
path("auction") {
put {
parameter("bid".as[Int], "user") { (bid, user) =>
// place a bid, fire-and-forget
auction ! Bid(user, bid)
complete((StatusCodes.Accepted, "bid placed"))
}
}
get {
implicit val timeout: Timeout = 5.seconds
// query the actor for the current auction state
val bids: Future[Bids] = (auction ? GetBids).mapTo[Bids]
complete(bids)
}
}
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
}
}
この例のJSONのマーシャリングとアンマーシャリングのロジックは、"spray-json"ライブラリ(これを使用する方法の詳細は JSON Support )で提供されています。
高レベルAPIの詳細については、 高レベルサーバーサイドAPI のセクションを参照してください。
低レベルのHTTPサーバーAPI
低レベルのAkka HTTPサーバAPIは、 HttpRequest
を受け取り、 HttpResponse
を生成することによって応答することによって、接続や個々のリクエストを扱うことを可能にします。 これは akka-http-core
モジュールによって提供されます。 関数呼び出しや Flow[HttpRequest, HttpResponse, _]
などのリクエストレスポンスを処理するためのAPIが用意されています。
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
}
}
高レベルAPIの詳細については、 低レベルサーバーサイドAPI のセクションを参照してください。
HTTPクライアントAPI
クライアントAPIは、Akka HTTPサーバが使用する同じ HttpRequest
および HttpResponse
の抽象化を使用してHTTPサーバを呼び出すメソッドを提供しますが、サーバーへのTCP接続を再利用することにより、同じサーバーへの複数の要求をより効率的に処理できるようにする接続プールの概念が追加されています。
単純なリクエストの例:
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import scala.concurrent.Future
import scala.util.{ Failure, Success }
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val responseFuture: Future[HttpResponse] =
Http().singleRequest(HttpRequest(uri = "http://akka.io"))
クライアントAPIの詳細については、 HTTPベースのサービスを使う(クライアントサイド) のセクションを参照してください。
Akka HTTPを構成するモジュール
Akka HTTPはいくつかのモジュールで構成されています。
- akka-http
サーバー側でHTTPベースのAPIを定義するためのパワフルなDSLと同様に、(非)マーシャリング、(非)圧縮のような高レベルの機能は、Akka HTTPでHTTPサーバーを作成するための推奨方法です。 詳細は、 高レベルサーバーサイドAPI のセクションにあります。
- akka-http-core
HTTP(WebSocketsを含む)の詳細については、主に低レベルのサーバー側とクライアント側の実装がセクションにあります 低レベルサーバーサイドAPI と HTTPベースのサービスを使う(クライアントサイド)
- akka-http-testkit
サーバー側のサービス実装を検証するためのテストハーネスと一連のユーティリティ
- akka-http-spray-json
カスタム型をJSONから、またはJSONへ、(デ)シリアライズを行うためのあらかじめ定義されたglue-code 詳細はここにあります :ref:`akka-http-spray-json`_
- akka-http-xml
scala-xmlでカスタム型をXMLから、または、XMLへ、(デ)シリアライズするための事前定義されたglue-code 詳細は :ref:`akka-http-xml-marshalling`_ を参照してください。
Contents