7.21.2014

Scala: Getting Started with Akka-HTTP

Scala: Akka-HTTP を試してみる

 

Akka-HTTP とは、Spray++ という位置づけの新進気鋭の HTTPサーバ フレームワーク。
Reactive Stream を使って実装されているのが大きな特徴。

Spray は ベンチマークFinaglewai (Haskell) に圧勝している。
Akka-HTTP もその流れを引き継ぎ、ハイパフォーマンスサーバに適したフレームワークになるだろうと期待している。

これまで Spray すら使ったことがなかったのだが、少しだけ Akka-HTTP に触れてみる。

 

Hello World with Akka-HTTP


/ping にアクセスすると "PONG" を返すシンプルな Webサーバを作る。

project/Build.scala
import sbt._
import sbt.Keys._

object ApplicationBuild extends Build {
  lazy val buildSettings = Seq(
    organization := "com.github.mogproject",
    version      := "0.1.0-SNAPSHOT",
    scalaVersion := "2.11.1",
    scalacOptions ++= Seq(
      "-deprecation",
      "-unchecked",
      "-feature",
      "-encoding", "utf-8"
    ),
    javacOptions ++= Seq(
      "-source", "1.7"
    )
  )

  lazy val resolverSettings = Seq(
    resolvers ++= Seq(
      "Sonatype OSS Releases" at "http://oss.sonatype.org/content/repositories/releases",
      "Sonatype OSS Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots",
      "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
    ),
    libraryDependencies ++= Seq(
      "org.scalatest" %% "scalatest" % "2.2.0" % "test",
      "com.typesafe" % "config" % "1.2.1",
      "com.typesafe.akka" %% "akka-http-core-experimental" % "0.4",
      "com.typesafe.akka" %% "akka-stream-experimental" % "0.4"
    )
  )

  lazy val testSettings = Seq(
    parallelExecution in Test := true
  )

  lazy val root = Project(
    id = "akka-http-example",
    base = file("."),
    settings = super.settings ++ buildSettings ++ resolverSettings ++ testSettings
  )
}
src/main/resources/application.conf
akka {
  loglevel = INFO
}
src/main/scala/Boot.scala
import akka.io.IO
import akka.http.Http

import akka.actor.ActorSystem
import akka.pattern.ask
import akka.util.Timeout
import akka.http.model._
import akka.http.model.HttpMethods._
import scala.concurrent.duration._
import akka.stream.scaladsl.Flow

object Boot extends App {
  import system.dispatcher
  implicit val system = ActorSystem()
  implicit val askTimeout: Timeout = 500.millis

  val bindingFuture = IO(Http) ? Http.Bind("localhost", 8080)

  bindingFuture foreach { binding =>
    Flow(binding.connectionStream) foreach { connection =>
      Flow(connection.requestProducer).map {
        case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
          HttpResponse(entity = "PONG!")
        case _ => HttpResponse(404, entity = "Unknown resource!")
      }.produceTo(connection.responseOut)
    }
  }
}
[error] akka-http-example/src/main/scala/Boot.scala:20: value connectionStream is not a member of Any
[error]     Flow(binding.connectionStream) foreach { connection ⇒
[error]                  ^
[error] one error found
[error] (compile:compile) Compilation failed
  • 以下のように修正したら、動いた
import akka.io.IO
import akka.http.Http

import akka.actor.ActorSystem
import akka.pattern.ask
import akka.util.Timeout
import akka.stream.{MaterializerSettings, FlowMaterializer}
import akka.http.model._
import akka.http.model.HttpMethods._
import scala.concurrent.duration._
import akka.stream.scaladsl.Flow

object Boot extends App {
  import system.dispatcher
  implicit val system = ActorSystem()
  implicit val askTimeout: Timeout = 500.millis

  val materializer = FlowMaterializer(MaterializerSettings())
  val bindingFuture = IO(Http) ? Http.Bind("localhost", 8080)

  bindingFuture foreach { case Http.ServerBinding(localAddress, connectionStream) =>
    Flow(connectionStream).foreach { case Http.IncomingConnection(remoteAddress, requestProducer, responseConsumer) =>
      Flow(requestProducer).map {
        case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
          HttpResponse(entity = "PONG!")
        case _ => HttpResponse(404, entity = "Unknown resource!")
      }.produceTo(materializer, responseConsumer)
    }.consume(materializer)
  }
}
$ curl http://localhost:8080/ping
PONG!
$ curl http://localhost:8080/pin
Unknown resource!

 

ルーティング

ロードマップによると、spray-routing に相当する DSLライブラリが akka-http (akka-http-core ではない) に組み込まれる予定とのこと。

現時点ではまだ無いのでパターンマッチで何とかするしかなさそう。

  • 公式ドキュメントにかかれていたサンプル
import HttpMethods._

val requestHandler: HttpRequest ⇒ HttpResponse = {
  case HttpRequest(GET, Uri.Path("/"), _, _, _) ⇒
    HttpResponse(
      entity = HttpEntity(MediaTypes.`text/html`,
        "<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 _: HttpRequest ⇒ HttpResponse(404, entity = "Unknown resource!")
}

 

References

0 件のコメント:

コメントを投稿