Akka-http on Google App Engine flexible environment

ScalaよりもGAE色が強くなってしまった、Scala advent calendarなのに...^^;

# はじめに：Google App Engine
元祖PaaSのGAE、忘れられた存在、、、かと思いきや、最近またよく耳にするようになってきました。
東京リージョンでの利用開始ニュースや主にGolangでの採用事例紹介などなど。

翻ってScalaエンジニア的にGAE/Javaってどうだろう？と考えると、バージョンがJava7だったりjettyベースだったりしてちょっと魅力に欠けるかなと個人的には思ってしまいます。

ただし、ドキュメントをよくよく読んでみると、GAEには2種類あり、これまでのStandard Environmentに加えベータではあるもののflexible environmentというのがあり（Standardはつい最近東京リージョンでも利用可能になりましたがベータはUSかヨーロッパのみとなります）こちらはJava8とあります。

https://cloud.google.com/appengine/docs?hl=ja

さらにドキュメントのAbout Java Runtime Environmentsのところを見ると２種類あることがわかります。

- [The Java 8 / Jetty 9.3 Runtime](https://cloud.google.com/appengine/docs/flexible/java/dev-jetty9?hl=ja)
- [The Java 8 Runtime](https://cloud.google.com/appengine/docs/flexible/java/dev-java-only?hl=ja)

Standard同様jetty版があるのはいいとして、 **単なるJava8の実行環境だけというのもあります！**

あら、もしかしてakka-http動くんじゃないの？と思ってやってみた、というのがこの記事となります。

## 稼働させるための条件
上記のリンクを読むと、GAE flex環境でアプリケーションを稼働させるには以下の３つあればいける模様。

- app.yaml
- 8080ポートでlistenするmainプログラムを含んだjar（各種ライブラリを含んだfat-jar）
- Dockerfile
  - なおGAE flexible environment自体のDockerfileは ~~[こちらの模様](https://github.com/GoogleCloudPlatform/appengine-java-vm-runtime/blob/master/gke-debian-openjdk/src/main/docker/Dockerfile)~~ ではなく[こちら](https://github.com/GoogleCloudPlatform/openjdk-runtime)に移動
    - 2016/12/1現時点のopen-jdkのバージョンは1.8.111です。

# 準備
事前に以下２点を行っておく必要があります。

- GAE上でプロジェクトを作っておく
- [Cloud SDK](https://cloud.google.com/sdk/)を開発PCにインストールしておく
  - GAEにデプロイするのに使用する `gcloud` コマンドを含む

※あと今回のscalaコードではslack認証をしているので、サンプルコードを動かすにはSlack上でアプリケーションを登録し、client_idおよびclient_secretを取得しておく必要があります。

# ソースコード
まずはじめにディレクトリ構成は以下。

```
.
├── Dockerfile
├── app.yaml
├── build.sbt
└── src
    └── main
        ├── resources
        └── scala
            └── my
                └── Main.scala
```

### app.yaml
とりあえず必要最低限だけ。

```yaml:app.yaml
runtime: custom
env: flex
```

### Dockerfile
自分で作るfat-jarの名前はmygae.jarとします。

```:Dockerfile
FROM gcr.io/google_appengine/openjdk8
VOLUME /tmp
ADD mygae.jar app.jar
CMD [ "java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
```

### build.sbt
scalaは最新の2.12.0を、akka-httpも最新の10.0.0を。
なおfat-jarを作るために[sbt-assembly](https://github.com/sbt/sbt-assembly)プラグインも別途入れております。

```scala:build.sbt
scalaVersion := "2.12.0"

libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.0.0"

assemblyJarName in assembly := "mygae.jar"
mainClass in assembly := Some("my.Main")
```

### Main.scala
Slack認証する部分をサンプルコードとしてみました。
`slauth` というエンドポイント部分はakka-httpのサーバーとしてのプログラム例として、またそこからSlackのoauth APIを呼び出す部分はakka-httpのクライアント例として見ることが出来ます。
本来は返ってきたjson文字列をcase classにunmarshalするようにすべきですがそこはサボって単に画面にjson文字列をベタッと表示するだけのシンプルなものにしています。

```scala:Main.scala
package my

import akka.actor.ActorSystem
import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.{Http, HttpExt}
import akka.stream.ActorMaterializer
import akka.util.ByteString

import scala.concurrent.Await
import scala.concurrent.duration._
import scala.util.{Failure, Success}

object Main extends App {

  implicit val actorSystem = ActorSystem()
  implicit val ec = actorSystem.dispatcher
  implicit val actorMaterializer = ActorMaterializer()

  val http: HttpExt = Http(actorSystem)

  object Controller {

    val route: Route =
      path("slauth") {
        get {
          parameters('code) { code =>
            onComplete(
              http.singleRequest(HttpRequest(uri = s"https://slack.com/api/oauth.access?client_id=クライアントID&client_secret=クライアントシークレット&code=$code")).flatMap {
                case HttpResponse(StatusCodes.OK, headers, entity, _) =>
                  entity.dataBytes.runFold(ByteString(""))(_ ++ _).map(_.utf8String)
                case resp @ HttpResponse(httpCode, _, _, _) =>
                  sys.error(s"fail: $httpCode")
              }
            ) {
              case Success(result) => complete(result)
              case Failure(_) => complete(StatusCodes.InternalServerError)
            }
          }
        }
      }
  }

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

  sys.addShutdownHook {
    val awaitable = for {
      binding <- bindingFuture
      _ <- binding.unbind()
      _ <- actorSystem.terminate()
    } yield ()

    Await.ready(awaitable, 10 seconds)
  }
}
```

なお、 `bindAndHandle` メソッドの第二引数部分は `"0.0.0.0"` とします。ここは `localhost` とか `10.0.0.1` とかだと動かないので注意が必要です。

# デプロイ

- コンソール上で `sbt assembly` 実行
- 出来た`mygae.jar`をtargetディレクトリから `app.yaml`や`Dockerfile`が配置してあるプロジェクト直下のディレクトリに移動
- コンソール上で `gcloud app deploy`

初回は少し時間がかかりますが、２回目以降は3-4分くらいでデプロイ出来ます。

# 実行
ブラウザから  `https://slack.com/oauth/authorize?client_id=....&scope=...&redirect_uri=...` のURLを入力してSlackの認証画面を表示し「Authorize」ボタンを押下すれば、ブラウザ上にjson文字列が表示されるはずです！

# 終わりに
scalaを稼働させる新たな基盤候補が見つかってよかったです。

# 追記
ログ出力は以下のAPI使ってやるのかな、と思っていたのですが
http://qiita.com/mtoyoshi/items/d11bcfa6f7ed51e5a813
こちらの方式のほうが簡単ですね。
http://qiita.com/chidakiyo/items/1452ab1ea83279a8e485