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

# 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](https://developer.lightbend.com/start/?group=akka&project=akka-http-quickstart-scala) からパッケージバージョンを選んで、「Create a project for me!」をクリックすると、プロジェクトをダウンロードすることもできる。

また他にも、自動的に設定が済んでいる新しい Akka HTTP のプロジェクトを [Giter8 template](http://www.foundweekends.org/giter8/) を使って、作ることもできる。

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

他にも、[テンプレートプロジェクト](https://github.com/akka/akka-http-quickstart-scala.g8) があるので、好みに合うものも探してみよう。


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

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

各々の「ルート」は1つ以上の「Directives」の段階から成っていて、特定のリクエストをハンドリングする。
「Directives」の詳細に関しては [こちらのドキュメント](https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/index.html) に詳しい。

以下に、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](https://doc.akka.io/docs/akka-http/current/introduction.html#routing-dsl-for-http-servers) にあるように `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](https://doc.akka.io/docs/akka-http/current/introduction.html#routing-dsl-for-http-servers) に従う形でしばらく進んでいきます。


### 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 の引数

```diff
- 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 の設定

```diff
- implicit val executionContext = system.executionContext
+ implicit val executionContext = system.dispatcher
```

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

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

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

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

```

で使うみたいです。


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

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

```


## Links

* [Akka HTTP introduction 日本語訳](https://qiita.com/daichildren98/items/8881ba8103351fce6e66)
* [Akka HTTP introduction](https://doc.akka.io/docs/akka-http/current/introduction.html#routing-dsl-for-http-servers)

* [Directives (Scaladoc?)](https://doc.akka.io/api/akka-http/10.2.0/akka/http/scaladsl/server/Directives.html)
* [Directives (AkkaHttpDocument?)](https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/index.html)

* [Directives#complete](https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/route-directives/complete.html)

* [ルーティングDSLの概要](https://akka-ja-2411-translated.netlify.app/scala/http/routing-dsl/overview.html)

* [Get Started with Lightbend Technologies](https://developer.lightbend.com/start/?group=akka&project=akka-http-quickstart-scala)