LoginSignup
1
2

More than 3 years have passed since last update.

Akka HTTP を使って HTTPサーバ入門

Posted at

Using Akka HTTP (Akka HTTP を使ってみよう)

諸注意:

Akka HTTP は、独自のリリースサイクルのもとで Akka そのものとは独立したモジュールとして提供されている (らしい)。(ググる時などは注意したい)

Akka HTTP は Akka HTTP 10.2.x の間にリリースされた、Akka 2.5, 2.6 および、これ以降の 2.x のバージョンと互換性があるのだそうである。

Akka HTTP モジュールは、akka-actor や akka-stream には依存しない。
なので使用者は、互換性のある Akka のバージョンを選ぶ必要や、akka-stream のバージョンの依存関係などを自分で定義する必要があるのだそうだ。

(sbt を使っている場合は, build sbt に以下のように定義する, 詳細は後述)

build.sbt
val AkkaVersion = "2.6.8"
val AkkaHttpVersion = "10.2.0"
libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-stream" % AkkaVersion,
  "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion
)

使うだけなら

Lightbend Getting Started page からパッケージバージョンを選んで、「Create a project for me!」をクリックすると、プロジェクトをダウンロードすることもできる。

また他にも、自動的に設定が済んでいる新しい Akka HTTP のプロジェクトを Giter8 template を使って、作ることもできる。

sbt new akka/akka-http-quickstart-scala.g8

他にも、テンプレートプロジェクト があるので、好みに合うものも探してみよう。

高レベルAPI を使って、サクッと Routing を設定して、HTTP サーバを立ててみよう

Akka HTTP では、ハイレベルな HTTP routing を記述するライブラリ (DSL?) を提供している。
この使い方をまずは見ていこう。

各々の「ルート」は1つ以上の「Directives」の段階から成っていて、特定のリクエストをハンドリングする。
「Directives」の詳細に関しては こちらのドキュメント に詳しい。

以下に、1つだけのパスにリクエストされた時に、マッチするルートを持つ例を示す。

これは /hello というエンドポイントにアクセスをされた時にのみ、マッチする。
そして、マッチしたら、HTTP レスポンスの OK を意味する String を返す。

「ルート」は Route DSL を使って、port をバインディングすることで、HTTP リクエストを受け取れるようになる。

プロジェクトの作成

# プロジェクトのディレクトリを作成
mkdir route-sample
cd route-sample

# build.sbt を作っておく
touch build.sbt

# ソースを配置するディレクトリを作成
mkdir -p src/main/scala

build.sbt を書く

build.sbt
name := "route-sample"
version := "0.1"
scalaVersion := "2.13.1"

val AkkaVersion = "2.6.8"
val AkkaHTTPVersion = "10.2.0"

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-stream" % AkkaVersion,
  "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion,     // (*1)
  "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion,  // (*2)
)

ここで、注意です。
introduction にあるように akka.actor.typed.ActorSystem を import して使う場合は (*2) の "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion, // (*2) が必要です。

"com.typesafe.akka" %% "akka-http" % AkkaHttpVersion, // (*1) を使う場合は、import と port のバインドあたりが変わります。こっちでやる場合に関しては後述します。

ひとまず、build.sbt には "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion, // (*2) がある前提、Routing は introduction に従う形でしばらく進んでいきます。

Routing とサーバの受付部分を書く

src/main/scala/Route.scala
import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives.{complete, get, path}

import scala.io.StdIn

object HttpServer {

  def main(args: Array[String]): Unit = {

    implicit val system = ActorSystem(Behaviors.empty, "my-server")

    implicit val executionContext = system.executionContext

    val route =
      path("hello") {
        get {
          complete(HttpEntity(
            ContentTypes.`text/html(UTF-8)`,
            "<h1>Hello, this is my-server</h1>"
          ))
        }
      }

    val host = "localhost"
    val port = 8080

    val bindingFuture = Http().newServerAt(host, port).bind(route)

    println(s"Server online at http://localhost:8080/hello\nPress RETURN to stop...")
    StdIn.readLine()
    bindingFuture
      .flatMap(_.unbind()) // port をアンバインド
      .onComplete(_ => system.terminate()) // system を停止
  }
}

実行

# current directory は route-sample です
$ sbt run 
[info] Loading global plugins from $HOME/.sbt/1.0/plugins
[info] Loading project definition from .../route-sample/project
[info] Loading settings for project route-sample from build.sbt ...
[info] Set current project to route-sample (in build file:.../route-sample/)
[info] Compiling 1 Scala source to .../route-sample/target/scala-2.13/classes ...
[info] running HttpServer 
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Server online at http://localhost:8080/hello
Press RETURN to stop...

と出てくるので http://localhost:8080/hello にアクセスしてみましょう。
Hello, this is my-server と出てくると嬉しい気持ちになりますね。

サーバを停止する場合は、Enter キーを押すと

...
Server online at http://localhost:8080/hello
Press RETURN to stop...


[success] Total time: 28 s, completed 2020/08/29 13:54:52
$ 

となって無事停止すると思います。

とてもシンプルでしたが、ここまでで一応 Request-Responce までを体験できたと思います。

typed を使わない場合

先で、

"com.typesafe.akka" %% "akka-http" % AkkaHttpVersion, // (*1) を使う場合は、import と port のバインドあたりが変わります。こっちでやる場合に関しては後述します。

と言いました。

akka.actor.typed.ActorSystem ではなく、akka.actor.ActorSystem で行う方法についても説明します。

build.sbt
name := "route-sample"
version := "0.1"
scalaVersion := "2.13.1"

lazy val AkkaVersion = "2.6.8"
lazy val AkkaHTTPVersion = "10.2.0"

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-stream" % AkkaVersion,
  "com.typesafe.akka" %% "akka-http" % AkkaHTTPVersion,
)

このとき、サーバのプログラム (src/main/scala/HttpServer.scala) は以下のようになります。

HttpServer.scala
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives.{complete, get, path}

import scala.io.StdIn

object HttpServer {

  def main(args: Array[String]): Unit = {

    implicit val system = ActorSystem("my-server")

    implicit val executionContext = system.dispatcher

    val route =
      path("hello") {
        get {
          complete(HttpEntity(
            ContentTypes.`text/html(UTF-8)`,
            "<h1>Hello, this is my-server</h1>"
          ))
        }
      }

    val host = "localhost"
    val port = 8080

    val bindingFuture = Http().newServerAt(host, port).bind(route)

    println(s"Server online at http://localhost:8080/hello\nPress RETURN to stop...")
    StdIn.readLine()
    bindingFuture
      .flatMap(_.unbind()) // port をアンバインド
      .onComplete(_ => system.terminate()) // system を停止
  }
}

具体的に変更した箇所は、以下の2箇所です。

  • ActorSystem の引数
- implicit val system = ActorSystem(Behaviors.empty, "my-server")
+ implicit val system = ActorSystem("my-server")

akka.actor.ActorSystem では、作成するシステムの名称のみを String 型の引数で与えれば良いです。

一方、akka.actor.typed.ActorSystem では、上記と Behaviors (akka.actor.typed.scaladsl.Behaviors) も合わせて渡す必要があるみたいです。

  • ExecutionContext の設定
- implicit val executionContext = system.executionContext
+ implicit val executionContext = system.dispatcher

akka.actor.ActorSystem では、ExecutionContext を system.dispatcher で設定します。

一方、akka.actor.typed.ActorSystem では、system.executionContext で設定するみたいです。

ExecutionContext は、私も勉強途中なのですが、とりあえずの用途としては

    bindingFuture
      .flatMap(_.unbind()) // port をアンバインド
      .onComplete(_ => system.terminate()) // system を停止

で使うみたいです。

最終的なプロジェクト構成

./route-sample
├── build.sbt
├── project
│   ├── build.properties
│   ├── project
│   └── target
├── src
│   └── main/scala/HttpServer.scala
└── target

Links

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2