Akka HTTPをAzureの無料プランで稼働させる (Scala)

(この記事は [フルスクラッチでAkka HTTPを起動させる (Scala)](https://qiita.com/ovrmrw/items/5acd308459e32d016787) の続きです)

ちきさんです。

[前回](https://qiita.com/ovrmrw/items/5acd308459e32d016787) はAkka HTTPを起動させることができたので今度はこれをデプロイしてみます。

デプロイ先は[Azure Web Apps](https://azure.microsoft.com/ja-jp/services/app-service/web/)で、**無料プランで稼働させてみます。**

実際に稼働しているものはこちら → https://akka-http-webapp-171210164221.azurewebsites.net/hello
(無料プランではアイドル状態が続くとアンロードされてしまうため、起動が遅いときがあります)

---

## WebServer.scalaを編集

デプロイ時はポートをAzureの環境変数(`HTTP_PLATFORM_PORT`)から取得する必要があるので

```scala
    val port: Int =
      (try {
        Option(sys.env("HTTP_PLATFORM_PORT")).map(_.toInt)
      } catch {
        case _: Exception => None
      }).getOrElse(8080)

    val bindingFuture = Http().bindAndHandle(route, "0.0.0.0", port)
```

ポートに関する記述はこのように変更します。またホストネームのところは`0.0.0.0`にします。

最終的には下記のようなコードになりました。

```scala:WebServer.scala
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.concurrent.ExecutionContextExecutor
import scala.io.StdIn

object WebServer {
  def main(args: Array[String]) {

    implicit val system: ActorSystem = ActorSystem("my-system")
    implicit val materializer: ActorMaterializer = ActorMaterializer()
    // needed for the future flatMap/onComplete in the end
    implicit val executionContext: ExecutionContextExecutor = system.dispatcher

    val route =
      path("hello") {
        get {
          val html = s"<h1>Say hello to akka-http</h1>"
          complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, html))
        }
      }

    val port: Int =
      (try {
        Option(sys.env("HTTP_PLATFORM_PORT")).map(_.toInt)
      } catch {
        case _: Exception => None
      }).getOrElse(8080)

    val bindingFuture = Http().bindAndHandle(route, "0.0.0.0", port)

    println(s"Server online at http://localhost:$port/\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
  }
}
```


## sbt-assemblyを追加

sbt-assemblyは一つのjarファイルを生成してくれます。Webpackみたいなものだと思えばOKです。

`project`ディレクトリに`plugins.sbt`ファイルが無い場合はまず作ります。そして下記の記述を追加します。

```sbt:project/plugins.sbt
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
```

## build.sbtを編集

`build.sbt`ファイルに下記の記述を追加します。

```
assemblyOutputPath in assembly := file(s".build/${name.value}.jar")
```

僕の`build.sbt`ファイルは下記のようになりました。

```sbt:build.sbt
name := "my-akka"

version := "0.1"

scalaVersion := "2.12.4"

libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.1.0"
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.5.11"

assemblyOutputPath in assembly := file(s".build/${name.value}.jar")
```

`build.sbt`は「Webpackの`webpack.config.js`みたいなものを兼ねている」と思えばOKです。

これにより`{root}/.build/my-akka.jar`にjarファイルが出力されるようになりました。

## Azure Toolkit for IntelliJを導入

ref: https://docs.microsoft.com/ja-jp/java/azure/intellij/azure-toolkit-for-intellij-installation

Azureに簡単にデプロイできるようになるプラグインがIntelliJにはあるのでこれを使います。

## IntelliJのProject Structureを編集

IntelliJを起動している状態で

- `File` => `Project Structure`
- `Artifacts`をクリック
- `+` => `JAR` => `Empty`
- `Name`
    - `unnamed`から`my-akka`に変更
- `Output directory`
    - `{省略}/my-akka/classes/artifacts/my_akka`から`{省略}/my-akka/.build/`に変更
- `OK`をクリック

これにより`{root}/.build/my-akka.jar`がデプロイする対象になります。
なのでデプロイ時にこのファイルが存在していなかったらエラーになります。(多分)

## ビルド

`npm run build`みたいなものです。入力するコマンドは

```
$ sbt assembly
```

これだけです。しばらく待つと`{root}/.build/my-akka.jar`が出来上がるので

```
$ java -jar .build/my-akka.jar
```

で実際に動かすことができます。
逆にここでちゃんと動かなかったらデプロイしても動かないということになります。

## デプロイ

IntelliJのプロジェクトペインで

- プロジェクト名を右クリック
- コンテキストメニューの一番下にある `Azure` => `Run on Web App`
- `Create New Web App`を選択 
    - (2回目以降は`Use Existing Web App`を選択)
- [IntelliJ を使用して Azure 用の Hello World Web アプリを作成する](https://docs.microsoft.com/ja-jp/java/azure/intellij/azure-toolkit-for-intellij-create-hello-world-web-app) を参考にして設定をする

**`Pricing Tier`で`Free`を選べば無料プランになります。** 
ちなみに`Free`プランで作成できるWeb Appsは1アカウントにつき10個までです。

これでデプロイが完了すればブラウザで動作確認できるようになります。

## まとめ

デプロイするまでにやることをまとめました。

- ポート番号をAzureの環境変数から取得するようにコードを変更する。
- sbt-assemblyプラグインを依存関係に加える。
- `build.sbt`を編集してビルドするファイルの出力先を変更する。
- IntelliJ用のAzureプラグインをインストールする。
- `Project Structure`の`Artifacts`の設定をする。
- `$ sbt assembly`でビルドする。
- IntelliJのAzureプラグインでデプロイする。

---

## おまけ

Azure Web Appsにデプロイする場合、`web.config`が適切に設定されている必要があります。通常であればIntelliJのAzureプラグインにより自動生成されますが、なんらかの事情により`web.config`が失われてしまった場合は下記のコードをコピペして復元してください。

```xml:web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" />
    </handlers>
    <httpPlatform processPath="%JAVA_HOME%\bin\java.exe"
        arguments="-Djava.net.preferIPv4Stack=true -Dserver.port=%HTTP_PLATFORM_PORT% -jar &quot;%HOME%\site\wwwroot\ROOT.jar&quot;">
    </httpPlatform>
  </system.webServer>
</configuration>
```