mike-neckのブログ

Java or Groovy or Swift or Golang

herokuでSpark2.0.0を動かしてみる #herokujp

こんにちわ、みけです。

不勉強なもので、

SinatraライクなJavaのwebアプリケーション・サーバーの

Sparkなるものがあるということを、

きしださんのブログで初めて知ったので、

試してみました。


準備

maven centralレポジトリーにartifactがあるので、

それを入手します。

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.sparkjava:spark-core:2.0.0'
}

コードを書く

コードは至ってシンプルで、

を記述します。

package sample;

import static spark.Spark.*;

public class SparkSampleApplication {

    public static void main(String[] args) {
        get("/", (req, res) -> "<!DOCTYPE html>" +
                "<html>" + "<head>" +
                "<title>" + "spark sample" + "</title>" +
                "</head>" +
                "<body>" +
                "<h1>" + "Hello, World!" + "</h1>" +
                "</body>" + "</html>");
    }
}

実行する

public static void mainに記述しているので、

サーブレットコンテナとかの設定なしに、すぐに起動できます。

localhostのポート4567にアクセスすれば、

上記で作成したページを見ることができます。

f:id:mike_neck:20140710011046p:plain

超お手軽です。


herokuで動かすための工夫

ローカルで遊ぶ分には、ここまでの手順で十分ですが、

せっかくなのでherokuにのせましょう。

以前、僕が書いた記事

Gradleでherokuアプリケーションを作成する

に基いて、herokuアプリ化していきます。

気をつけるポイントは

  1. herokuに乗せる場合にはポート番号を環境変数から取得する必要がある(もちろんローカルでは環境変数にないのでnullの扱いをうまくやる必要がある)
  2. public static void mainで始まるアプリケーションなのでmanifestを準備してエントリーポイントを設定する必要がある
  3. gradle buildだけでは必要ライブラリーが同梱されないので、fat jarを作る必要がある。
  4. その他もろもろ

ポート番号の扱い

nullな値も入ってくる可能性を考慮して、

ここはJava8らしくOptional<V>を使いましょう。

先ほどのルーティングを設定するコードの直前にポートの設定を行います。

import java.util.Optional;

import static spark.Spark.*;

public class SparkSampleApplication {

    public static void main(String[] args) {
        Optional<String> optionalPort = Optional.ofNullable(System.getenv("PORT"));
        optionalPort.ifPresent(p -> {
            int port = Integer.parseInt(p);
            setPort(port);
        });
        /* 以下略 */
    }
}

環境変数$PORTに値が設定されていれば、

それをSparkBase#setPortに渡して、port番号設定します。

System.getenv("PORT")環境変数$PORTが設定されていなければ、

Optional<String>#ifPresent(Consumer)でConsumerの処理が実行されません。

よってポートはデフォルトの値が利用されます。

なお、SparkBaseのstaticメソッドを読んでいるはずなのに、

importにSparkしかないのは、

SparkクラスがSparkBaseクラスを継承したクラスであるためです。

manifestファイルの作成

これはgradleの書き方でいくらでも作れます。

jar {
    manifest {
        attributes('Main-Class' : 'sample.SparkSampleApplication')
    }
}

先ほどのpublic static void mainメソッド

エントリーポイントとして認識するmanifest.fileが

作成されます


fat jarの作成

gradleのbuildでは、特に設定がなければ、

プロジェクトのjarを作るだけです。

これをProcfileで一々-cpオプションをつけて

ライブラリーのリンクをするのも面倒なので、

fat jarにしました。

fat jarを作るbuild.gradleは次のような感じになります。

jar {
    from {
        configurations.compile.collect {
            it.isDirectory()? it : zipTree(it)
        }
        configurations.runtime.collect {
            it.isDirectory()? it : zipTree(it)
        }
    }
}

その他もろもろ

stageタスクの作成

herokuはgradleアプリケーションの場合stageタスクを呼び出すので、

そこでビルドをさせます。

task stage (dependsOn: 'clean') {
    finalizedBy build
}

最初task stage(dependsOn: ['clean', 'build'])と書いてみたのですが、

なんかstageタスクが終了するとbuildディレクトリがごっそりなくなっている

という謎の事態が発生したので、

finalizedBybuildタスクを最後に実行するようにしています。

なお、heroku上でテストを実行したくない場合は、

stage.finalizedBy jar

でもよいです。

Procfileファイルの作成

Procfileにはアプリケーションを起動する場合のコマンドを書いておきます。

今回は実行可能jarを作成したので、それを起動するコマンドを書きます。

web: java $JAVA_OPTS -jar build/libs/project-name-version.jar
system.propertiesファイルの作成

これ重要です。

今回はJava8で動かしますので、system.propertiesにもJava8を指定するように書きます。

java.runtime.version=1.8
herokuアプリの作成

以下、git管理において、herokuアプリを作成して、herokuにプッシュします。

$ git init
$ git add .
$ git commit -m 'first commit'
$ heroku apps:create your-app-name
$ git push heroku master

まとまらないまとめ

こんな感じで作成したのが、こちらです。

トータルで15分くらいでアプリを公開出来ました。

Javaが冗長でアプリを作るのに時間がかかるとdisってる皆さんも、

一度は試されてみてはいかがでしょうか?