Heroku上でJersey + Jetty + Gradleを動かす

はじめに


JavaでRestfulといえば、JAX-RSが有名ですね。

それを扱いやすくしたFrameworkといえば、Jerseyになります。
これをHerokuで動かすようにしてみます。

ついでにオワコン臭漂うmavenを辞めて、Gradleでビルドができるようにもしてみます

目標


以下ができていること

  • Heroku上でJerseyが動くこと
  • HerokuではJettyがサーブレット・コンテナとして動くこと
  • ビルドツールには、デフォルトのmavenではなくGradleであること

Jersey


まずはmaven ArchTypeから必要なファイルを準備

ここを参考にすると、ArchTypeは2種類あることが分かります。

今回はJettyで動かすので、サーブレットコンテナ用のものを使います

1
mvn archetype:generate -DarchetypeGroupId=org.glassfish.jersey.archetypes -DarchetypeArtifactId=jersey-quickstart-webapp -DarchetypeVersion=2.17

必要項目を聞かれますので、以下のような感じで入力していきます

1
2
3
4
Define value for property 'groupId': : kurobara
Define value for property 'artifactId': : kurobara
Define value for property 'version': 1.0-SNAPSHOT:
Define value for property 'package': kurobara: kurobara

成功すれば、以下の構成になります

1
2
3
4
5
6
7
8
9
10
11
├── pom.xml
├── src
│   └── main
│       ├── java
│       │   └── kurobara
│       │       └── MyResource.java
│       ├── resources
│       └── webapp
│           ├── WEB-INF
│           │   └── web.xml
│           └── index.jsp

mavenの削除


普通に考えると、mavenを使わなければpom.xmlを残してもよいかと思います(自分も最初は思いました) Herokuではmavenがデフォルトで使われますので、削除してしまいます

1
rm pom.xml

どうやら、mavenとGradleの両方のビルドファイルが存在するとHerokuではmavenのものが最優先で使われるようです

Gradleの追加


以下の内容をbuild.gradleに記載します

このファイルのミソはtask stage(dependsOn: ['clean', 'installApp'])を記載していることです。 Herokuではこのタスクが必要なようです。

公式を参考にしました。

多分、必要無いと思いますが念のためgradlewも用意しました(自分は一応、これもファイルに追加しています)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
apply plugin: "java"
apply plugin: "war"
apply plugin: "jetty"
apply plugin: 'application'

group = "kurobara"
version = 1.0
sourceCompatibility = 1.7
mainClassName = "kurobara"
applicationName = "kurobara"

def defaultEncoding = 'UTF-8'
[
  compileJava,
  compileTestJava,
  javadoc
]*.options*.encoding = defaultEncoding

repositories {
  mavenLocal()
  maven { url "http://maven.seasar.org/maven2" }
  mavenCentral()
}

dependencies {
  compile 'org.eclipse.jetty:jetty-server:9.2.10.v20150310'
  compile 'org.eclipse.jetty:jetty-webapp:9.2.10.v20150310'
  compile 'org.glassfish.jersey.core:jersey-server:2.17'
  compile 'org.glassfish.jersey.containers:jersey-container-servlet-core:2.17'

  testCompile 'junit:junit:4.10'
}

task stage(dependsOn: ['clean', 'installApp'])

[jettyRun, jettyRunWar]*.httpPort = 8090
[jettyRun, jettyRunWar]*.contextPath = 'kurobara'


task wrapper(type: Wrapper) {
  gradleVersion = '1.6'
}

Jettyの準備


最低限度の内容でJettyサーバが動くようにします

mainメソッドにサーバの実装を記載します。

Gradleに記載したように、これがmainClassになります(mainClassNameを参照)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;

public class Main {

  public static void main(String[] args) throws Exception {
      Server server = new Server(Integer.valueOf(System.getenv("PORT")));

      WebAppContext context = new WebAppContext();
      context.setServer(server);
      context.setContextPath("/");
      context.setResourceBase("src/main/webapp");
      context.setClassLoader(Main.class.getClassLoader());
      server.setHandler(context);

      server.start();
      server.join();
  }
}

以下の構成になるようにMainクラスを配置します

1
2
3
4
5
6
7
8
9
10
11
12
├── build.gradle
├── src
│   └── main
│       ├── java
│       │   └── kurobara
│       │       ├── Main.java <- これを追加
│       │       └── MyResource.java
│       ├── resources
│       └── webapp
│           ├── WEB-INF
│           │   └── web.xml
│           └── index.jsp

動作確認


まずは、テストが動くかどうか確認

1
2
$gradle clean
$gradle test

次にアプリをビルドし、サーバを起動

1
2
3
$gradle clean
$gradle build
$gradle jettyRunWar

以下のコマンドでGot it!が返却されるか確認

1
curl -v http://localhost:8090/kurobara/webapi/myresource

Heroku環境用の準備


Herokuで動かすJavaのバージョンを指定するsystem.propertiesを作成
(以下のようにとりあえず確実に動作するバージョンを指定)

1
java.runtime.version=1.7

Procfileに記載するスクリプトファイルの確認

1
$gradle stage

こういう形にビルド結果が出力されているので、アプリケーション起動スクリプトのファイルパスを確認する

1
2
3
4
5
6
7
8
9
10
11
12
├── build
│   ├── classes
│   │   └── main
│   │       └── kurobara
│   │           ├── Main.class
│   │           └── MyResource.class
│   ├── dependency-cache
│   ├── install
│   │   └── kurobara
│   │       ├── bin
│   │       │   ├── kurobara <- これが起動用ファイル
│   │       │   └── kurobara.bat

生成したスクリプトファイルパスをProcfileに記載

1
web: ./build/install/kurobara/bin/kurobara

不要ファイルを無視するように.gitignoreを作成
(Herokuへのデプロイはgitのため)

1
2
build/
.gradle/

最終的に以下の構成になります

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
├── .gitignore
├── Procfile
├── build.gradle
├── src
│   └── main
│       ├── java
│       │   └── moonstruckdrops
│       │       ├── Main.java
│       │       └── MyResource.java
│       ├── resources
│       └── webapp
│           ├── WEB-INF
│           │   └── web.xml
│           └── index.jsp
└── system.properties

Herokuへdeploy


herokuへloginする

1
2
$ brew install heroku
$ heroku login

サーバへデプロイする

1
2
3
4
5
$ git init
$ git add .
$ git commit -m "Ready to deploy"
$ git remote add heroku your_heroku_path
$ git push heroku master

以下のコマンドでGot it!が返却されるか動作確認

1
$curl -v http://your_app.herokuapp.com/webapi/myresource

感想


書くと簡単でしたが、実際にやってみると思った以上に嵌まりどころが多かった

特に以下が盛大な罠でした

  • Procfileに書くファイルはどれなのか
  • heroku logsを確認するとapp crashと表示されて何がなんだかわからんかったこと
  • app crash起因ですがweb.xmlはいじらなくてもいいのか?なんて思ったこと

一度環境さえ準備できれば開発に専念できるようになるので、非常に楽ですね