Finatra使ってみた

どうもこんにちは、久しぶりのScala投稿です。
今回も安定のdocker上でScalaを動かしていきたいと思います。
どうでもいいけど「〜てみた」というタイトルYouTuber感出るのであれですね…


# 環境のセットアップ

アプリケーションのディレクトリ構造はこんな感じ
シンプルにmain.scala内でサーバーとコントローラを宣言してます。

```
build.sbt
buildcache
project/
src/main/scala/main.scala
```



とりあえずdocker単体のコンテナで動かします。

使用イメージ: `takashioshikawa/scala-2.11.7`
ポート: `9000:9000`
ボリューム: `$(pwd)/buildcache/.ivy2/cache:/root/.ivy2/cache`
ボリューム: `$(pwd)/buildcache/.sbt/boot:/root/.sbt/boot`
ボリューム: `$(pwd):/var/app`


くっっっっそ遅いビルドを少しでも高速化するためにキャッシュディレクトリをマウントしています。
これやっとかないとコンテナ消すたびに色々ダウンロードしだすので病みます
DockerでScalaを使う時のベストプラクティスがまだ分かってないので良い情報を持っている方はぜひ情報の方よろしくお願いします



最終的に実行するコマンドはこちら

```
docker run --rm -it -p 9000:9000 -v $(pwd)/buildcache/.ivy2/cache:/root/.ivy2/cache -v $(pwd)/buildcache/.sbt/boot:/root/.sbt/boot -v $(pwd):/var/app --name finatra-server takashioshikawa/scala-2.11.7
```

# build.sbtとmain.scalaファイル

コピーしてもらえば動きます、多分

```scala:build.sbt
lazy val root = (project in file(".")).
  settings(
    name := "finatra-server",
    version := "1.0",
    scalaVersion := "2.11.7",
    libraryDependencies ++= dervy,
    resolvers ++= resolve
  )

lazy val dervy = {
  Seq(
    "com.twitter" % "finatra-http_2.11" % "2.6.0"
  )
}

lazy val resolve = {
  Seq(
    Resolver.sonatypeRepo("releases"),
    "Twitter Maven" at "https://maven.twttr.com"
  )
}
```


```scala:src/main/scala/main.scala
import com.twitter.finagle.Http
import com.twitter.finagle.http.Request
import com.twitter.finagle.stats.NullStatsReceiver
import com.twitter.finagle.tracing.NullTracer
import com.twitter.finatra.http.filters.HttpResponseFilter
import com.twitter.finatra.http.routing.HttpRouter
import com.twitter.finatra.http.{Controller, HttpServer}

object Main extends FinatraBenchmarkServer

class FinatraBenchmarkServer extends HttpServer {
  // 色々な設定項目はBaseHttpServerを参照
  // override def defaultFinatraHttpPort: String = ":9000"
  // override def defaultHttpServerName: String = "0.0.0.0"

  override def configureHttpServer(server: Http.Server) = {
    server
      .withCompressionLevel(0)
      .withStatsReceiver(NullStatsReceiver)
      .withTracer(NullTracer)
  }

  override def configureHttp(router: HttpRouter): Unit = {
    router
      .filter[HttpResponseFilter[Request]]
      .add[FinatraBenchmarkController]
  }
}

class FinatraBenchmarkController extends Controller {
  private[this] val helloWorldResponseText = "Hello, World!"

  get("/json") { request: Request =>
    Map("message" -> "Hello, World!")
  }

  get("/plaintext") { request: Request =>
    helloWorldResponseText
  }
}
```



# finatraを起動する


コンテナ内に入っていると思うのでアプリケーションディレクトリに移動
`cd /var/app`


アプリケーションを起動してみましょう。

`-http.port=:9000`がポートの設定
`-http.name=:0.0.0.0`がホストの設定
`0.0.0.0`はdockerでlocalhostは使えないから

```
sbt 'run -http.port=:9000 -http.name=:0.0.0.0'
```

※FinatraBenchmarkServerに`override def defaultFinatraHttpPort = ":9000"`と書くとsbt run時じゃなくてもポートの設定が出来る
※FinatraBenchmarkServerに`override def defaultHttpServerName: String = "0.0.0.0"`と書くとsbt run時じゃなくてもサーバー名の設定ができる

上の2つを設定すると`sbt run`と実行するだけで`sbt 'run -http.port=:9000 -http.name=:0.0.0.0'`と同じ動作になる

なんか情報としてホストの設定が全然見つからなくてfinatraでは設定できない？と思ったけど普通にsbt runのエラーに出力されました

あとパラメータ入れる時は''で囲まないといけないっぽいので注意が必要です。

sbr 'run'の''の中に間違ったパラメータ入れると色々出てきます。

```
flags:
  -admin.announce='java.lang.String': Address for announcing admin server
  -admin.port=':9990': Admin http server port
  -cert.path='': path to SSL certificate
  -doc.root='': File serving directory/namespace for classpath resources
  -help='false': Show this help
  -http.announce='java.lang.String': Address for announcing HTTP server
  -http.name='http': Http server name
  -http.port=':8888': External HTTP server port
  -http.response.charset.enabled='true': Return HTTP Response Content-Type UTF-8 Charset
  -https.announce='java.lang.String': Address for announcing HTTPS server
  -https.name='https': Https server name
  -https.port='': HTTPs Port
  -key.path='': path to SSL key
  -local.doc.root='': File serving directory for local development
  -log.append='true': If true, appends to existing logfile. Otherwise, file is truncated.
  -log.async='true': Log asynchronously
  -log.async.inferClassNames='false': Infer class and method names synchronously. See com.twitter.logging.QueueingHandler
  -log.async.maxsize='4096': Max queue size for async logging
  -log.level='INFO': Log level
  -log.output='/dev/stderr': Output file
  -log.rollPolicy='Never': When or how frequently to roll the logfile. See com.twitter.logging.Policy#parse documentation for DSL details.
  -log.rotateCount='-1': How many rotated logfiles to keep around
  -maxRequestSize='5242880.bytes': HTTP(s) Max Request Size
  -mustache.templates.dir='templates': templates resource directory
  -shutdown.time='1.minutes': Maximum amount of time to wait for pending requests to complete on shutdown
```

ではサーバーが起動している状態で
main.scalaに設定されていたルーティングの`/json`を確認してみましょう

まずはdocker-machineのIPアドレスを確認して
`docker-machine ip あなたのVM名`

僕は`192.168.99.100`なので

`192.168.99.100:9000/json`
にアクセスします


ブラウザに表示された結果は以下の通りでした

```json:json
{"message":"Hello, World!"}
```

plaintextも設定していた文字列がそのまま表示されました。

設定していないエンドポイントにアクセスしようとすると特に何の設定もされていない404ページが表示されました


今回はサンプルのリクエストを叩いてみるだけで終了します
次回があるかわかりませんが何かメモしたくなったらまた書きます






# 所感
なんか公式見ながらやっても普通に動かない印象がありました笑
とりあえずコピペしたらサーバーくらい起動するくらいが理想なのでそのへんは割りと不親切かもしれません。
あと日本語情報が少ないので英語少しくらい読めたほうが良いはず
僕自身Sprayが昔好きでakka-httpやろうかなと思ってたんですけどふと目に入ってきたので触ってみました、APIに特化してると軽量スタートできるのが嬉しいです。

今回はただサンプルのリクエストを行っただけなので全然全体の触り心地はわからないですが、もう少し何か作ってみて判断したいと思います。


# 参考
[Finatra](http://twitter.github.io/finatra/ "Finatra")
[finatraはじめの一歩](http://qiita.com/FScoward/items/5f7037941a8c592bcb66 "finatraはじめの一歩")
[travis-ciのキャッシュ機能を使って、Scalaプロジェクトのビルドを少しだけ高速化する](http://d.hatena.ne.jp/xuwei/20150115/1421287447 "travis-ciのキャッシュ機能を使って、Scalaプロジェクトのビルドを少しだけ高速化する")
[Maven Repository finatra-http_2.11: 2.6.0](http://search.maven.org/#artifactdetails%7Ccom.twitter%7Cfinatra-http_2.11%7C2.6.0%7Cjar "Maven Repository finatra-http_2.11: 2.6.0")
[]( "")