Akka HTTPでWeb APIに仕立てる
はじめに
前回まではAkkaのアクターを使ってメッセージの送受信を行うアプリケーションについて紹介してきました。これらはサーバーサイドの中でもバックエンドを司る機能になります。今回は、WebシステムとしてHTTPのリクエストに対してレスポンスを返すためのエンドポイントとなるAkka HTTPを紹介します。
Akka HTTPとは
Akka HTTPはAkkaの拡張機能の一つで、その名の通りHTTPのリクエストを受け付け、処理結果などのレスポンスを返す機能を持ちます。以前紹介したPlay Frameworkは、Nettyというネットワークアプリケーションフレームワークを使っていました。一方Akka HTTPは、これまでに紹介したアクター(akka-actor)やAkka Streams(akka-stream)をベースとして独自に実装されており、Nettyのようなフレームワーク上に構築されているものではありません。また、Playもバージョン2.6からAkka HTTPを標準的に利用しており、Nettyを使っていたバージョンに比べ高速であると言われています。
Play Frameworkは画面を生成するViewの機能も備えるフルスタックなWebアプリケーションフレームワークであるのに対し、Akka HTTPは汎用的なツールキットです。REST APIを提供したいときや、クライアントサイドはモバイル向けのネイティブアプリケーションを構築し、サーバーサイドにWeb APIを作りたいときなどに便利です。
Akka HTTPを使ってみる
Akka HTTPを利用するには、他の拡張機能と同じようにビルドファイルbuild.sbtへライブラリへの依存関係を定義します。執筆時点の最新バージョン10.1.5を使用する場合は以下のように定義します。
libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-http" % "10.1.5" )
Akka(akka-actor)とはリリースサイクルが異なるため、バージョンも違います。
ルーティングDSL
ライブラリの設定が完了したので、実際に使ってみましょう。Akka HTTPのサーバーモジュールは、akka-http-coreとakka-httpの2つのレイヤから構成されます。akka-httpはakka-http-coreに依存するため、akka-http-coreを明示的依存関係に定義する必要はありません。akka-http-coreには低レベルの機能が、akka-httpには高レベルの機能が実装されています。細かな制御が柔軟にできる低レベルのAPIを利用することもできますが、ここではシンプルに実装できる高レベルのAPIを使用します。HTTPリクエストのルーティングは、Akka HTTPが提供するDSLを使って表現できます。
例えば、/pingのGETリクエストに対するふるまいは次のようにRouteを定義します。
val route: Route = path("ping") { get { // ここにふるまいを定義する log.info("receive GET request /ping") // ... // HTTP OKでレスポンスを返す complete(HttpEntity(ContentTypes.text/html(UTF-8), "<h1>pong</h1>")) } }
/pingへのGETリクエストに対して、単にpongという文字列を返しています。
サーバー起動
RouteにHTTPサーバーで待ち受けるURIを定義したので、次にHTTPサーバーを起動します。サーバーの起動はHttp().bindAndHandleメソッドの引数に、定義したRouteとバインドするホスト名・ポートを指定し呼び出します。必要に応じて、最大接続数などのサーバーへの細かな設定も指定できます。なおAkka HTTPは内部でAkka Streamsを使用するので、RunnableGraphを実行(run)するために暗黙のスコープにActorMaterializerを定義しておく必要があります。
// ルートの定義 object CafeServer extends App { val route: Route = ??? // RunnableGraphの実行(run)にActorMaterializerが必要 implicit val system = ActorSystem("CafeServer", ConfigFactory.load("cafe")) implicit val materializer = ActorMaterializer() // HTTPサーバーの起動 val bindingFuture = Http().bindAndHandle(route, "localhost", 8080) bindingFuture .map { serverBinding => log.info(s"Server online at ${serverBinding.localAddress}") log.info("Press RETURN to stop...") // RETURN が押されるまでサーバーを起動したままにする StdIn.readLine() serverBinding.unbind() serverBinding } .onComplete { _ ⇒ system.terminate() log.info("System terminated.") } //...
サーバー起動時にアドレスをログに出力し、標準入力よりEnterが送信されると停止するようにしました。では、sbt runでサーバーを起動したあと、HTTPリクエストを送信してみます。ここでは、リクエストとレスポンスの内容が分かりやすいHTTPie(リンクよりダウンロード可)というツールを使用し、HTTPリクエストを送信します。
sbt run
http GET http://localhost:8080/ping
HTTPステータス「200 OK」とともに結果「<h1>pong</h1>」が得られることでしょう。
> http GET http://localhost:8080/ping HTTP/1.1 200 OK Content-Length: 13 Content-Type: text/html; charset=UTF-8 Date: Mon, 10 Sep 2018 08:11:55 GMT Server: akka-http/10.1.5 <h1>pong</h1>
URIを定義したRoute型は次のように定義されており、RequestContextをFuture[RouteResult]に変換する関数のエイリアスです。
type Route = RequestContext => Future[RouteResult]
先の例のようにRouteDirectivesトレイトのcompleteを使ってHTTP OKでレスポンスを返す他に、リクエストを拒否(reject)したり、例外をスローしリクエストを失敗(failWith)させたり、リダイレクト(redirect)したりできます。
val route: Route = path("ping-reject") { // ここにふるまいを定義する log.info("receive GET request /ping-reject") // ... reject(AuthorizationFailedRejection) }
val route: Route = path("ping-fail") { // ここにふるまいを定義する log.info("receive GET request /ping-fail") // ... failWith(new IllegalArgumentException("oops!")) }
val route: Route = path("ping-redirect") { // ここにふるまいを定義する log.info("receive GET request /ping-redirect") // ... redirect("/ping", StatusCodes.PermanentRedirect) }
このようにディレクティブによりルートを作成できます。Akka HTTPには紹介した以外にも多くのディレクティブが用意されています(Akka公式サイト:Predefined Directives参照)。
マーシャリング・アンマーシャリング
リクエストで受信したデータを処理するため、あるいは、処理した結果をレスポンスするためにデータ形式の変換(マーシャリング・アンマーシャリング)が必要です。リクエストやレスポンスボディーのデータ形式とオブジェクト間の変換は、これらのルーティング定義とは切り離され、マーシャラー・アンマーシャラーで行われます。変換を行うためには、暗黙のスコープ内でこれらが有効となっている必要があります。そこでAkka HTTPでは、StringやByteStringをはじめ多くのマーシャラーとアンマーシャラーが予め定義されており、Marshallerオブジェクトにより提供され有効となっているため、個別にimportする必要はありません。予め定義されているマーシャラーとアンマーシャラーは、Akkaの公式サイトで確認できます(マーシャラー:Predefined Marshallers、アンマーシャラー:Predefined Unmarshallers)。また、定義されていない場合は、独自のマーシャラーを定義し、暗黙のスコープで有効にすることもできます。
JSONのマーシャリング・アンマーシャリング
例えば、JSON形式の場合はspray-jsonというライブラリがあります。これを利用してマーシャラーを定義してみましょう。
akka-http-spray-jsonへの依存関係を追加します。
libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.5" )
SprayJsonSupportトレイトをミックスインし、マーシャラーを定義します。
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import spray.json.DefaultJsonProtocol._ object CafeServer extends App with SprayJsonSupport { case class Order(product: String, count: Int) implicit val orderFormat = jsonFormat2(Order) // ...
Order型のケースクラスを定義したあと、暗黙のスコープにOrder型とJSON形式の間をマーシャリング・アンマーシャリングするマーシャラーを定義しました。ケースクラスとJSON型のマーシャリングはjsonFormat1、jsonFormat2、……、jsonFormat22のようにケースクラスの引数の数に対応したマーシャラーが定義されています。Order型は引数が2つのため、ここではjsonFormat2を使用しました。続いて、Orderを取得するGETリクエストとOrderを登録するPUTリクエストを定義します。
/pingのGETリクエストは、ケースクラスOrderをJSON形式にマーシャリング(変換)してレスポンスします。
PUTリクエストはJSON形式のリクエストをOrder型のケースクラスにアンマーシャリング(変換)して受けることができます。
val route: Route = path("order") { get { // ここにふるまいを定義する log.info("received GET request /order") // ... // Order型を返す complete(Order("Coffee", 10)) } ~ put { // Order型で受け取る entity(as[Order]) { order => // ここにふるまいを定義する log.info("received PUT request /order") // ... complete(s"Received order: ${order.product} * ${order.count}") } } }
ルートは「~」演算子で結合できます。ここではPUTとGETの2つのルートを結合しました。PUTリクエスト(Orderの登録)、GETリクエスト(Orderの参照)の順にHTTPリクエストを送信すると、次のような結果となります。
> http PUT http://localhost:8080/order product=Coffee, count:=10 HTTP/1.1 200 OK Content-Length: 27 Content-Type: text/plain; charset=UTF-8 Date: Mon, 10 Sep 2018 08:13:29 GMT Server: akka-http/10.1.5 Received order: Coffee * 10
PUTリクエストでOrderを送信すると、サーバー側でオーダーを登録(実装は省略)し、その結果が返ってきます。続いて、登録したOrderをGETリクエストで確認します。
> http GET http://localhost:8080/order HTTP/1.1 200 OK Content-Length: 31 Content-Type: application/json Date: Mon, 10 Sep 2018 08:14:05 GMT Server: akka-http/10.1.5 { "count": 10, "product": "Coffee" }
GETリクエストを送信すると、登録されたOrder型がJSON形式でレスポンスされました。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- 「PHP 7.3.0」リリース
- 「PHP 7.0.2/5.6.17/5.5.31」リリース
- 「PHP 7.0.3/5.6.18/5.5.32」リリース
- 「PHP 7.0.4/5.6.19/5.5.33」リリース
- 「PHP 7.1.6/7.0.20」リリース、「PHP 7.2 Alpha版」も
- 「PHP 7.1.6/7.0.20」リリース、「PHP 7.2 Alpha版」も
- 「PHP 7.0.9/5.6.24/5.5.38」リリース、PHP 5.5系列はサポート打ち切りへ
- 「PHP 7.1.5/7.0.19」リリース
- 「PHP 7.2.12/7.1.24」リリース
- 「PHP 7.2.11/7.1.23」リリース