新年明けましておめでとうございます。今年もよろしくお願いいたします。私は今回の年末年始のお休みの間、なるだけPCを開かないでおこうと心に決めていたのですが、前々から気になっていたHerokuを少しだけ触ってみようかな、などど思い立ってしまいました。
さて、HerokuにはJava・Ruby on Rails・Python/Djangoなど、いくつかのWebアプリケーション実行環境が用意されています。JavaのWebアプリケーションを実行するために、Herokuの公式ドキュメントではJettyを使用していますが、ここはGlassfishを動作させてみましょう。jarファイル1個で起動できるEmbedded Glassfishを使用します。
Mavenを使ってビルドを行いますので、まずはpom.xmlを作成します。packagingはwarとし、maven-war-pluginには以下のようにweb.xml不要の設定をします。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin>
さらに以下のdependencyを追加します。
<dependency> <groupId>org.glassfish.main.extras</groupId> <artifactId>glassfish-embedded-all</artifactId> <version>3.1.2.2</version> </dependency>
次にGlassfishを起動するクラスを作成します。リッスンポートは環境変数から取得するように設定しています。
public class Bootstrap { public static void main(String[] args) { try { GlassFishProperties gfProps = new GlassFishProperties(); gfProps.setPort("http-listener", Integer.parseInt(System.getenv("PORT"))); final GlassFish glassfish = GlassFishRuntime.bootstrap() .newGlassFish(gfProps); glassfish.start(); ScatteredArchive war = new ScatteredArchive("myApp", ScatteredArchive.Type.WAR, new File("src/main/webapp")); war.addClassPath(new File("target/classes")); glassfish.getDeployer().deploy(war.toURI()); Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run() { try { System.out.println(glassfish + " shutdown now!!"); glassfish.dispose(); } catch (GlassFishException e) { e.printStackTrace(); } } }); } catch (GlassFishException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
続いて、基本ということでサーブレットを作成しましょう。
@WebServlet(name = "MyServlet", urlPatterns = "/MyServlet") public class MyServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); out.println("Hello, Embedded-Glassfish!!"); } @Override public void init() throws ServletException { System.out.println("init: " + this); } @Override public void destroy() { System.out.println("destroy: " + this); } }
一度ローカル環境でビルドして実行してみます。(クラスパス中、sample-projectの部分は適宜読み替えてください)
$ mvn clean package $ export PORT=18080 $ java -cp "target/classes:target/sample-project/WEB-INF/lib/*" Bootstrap
うまく起動できたら、別のコンソールを開いてサーバの応答を確認します。
$ curl "http://localhost:18080/myApp/MyServlet" Hello, Embedded-Glassfish!!
Herokuで稼働させるためには、もう1つファイルを追加する必要があります。プロジェクトのディレクトリ直下に"Procfile"という名前のテキストファイルを作成し、先ほどのjavaコマンドを記載します。
web: java -cp target/classes:target/sample-project/WEB-INF/lib/* Bootstrap
また、これは必須ではありませんがJDKのバージョンを指定したいときには、これもプロジェクトのディレクトリ直下、"system.properties"ファイルに以下の内容を記載します。
java.runtime.version=1.7
これで設定はできました。後はGitにcommitし、Herokuアプリケーションとしてpushするだけです。
$ git init $ git add . $ git commit -m "init" $ heroku create $ git push heroku master
pushすると、Heroku側でビルドが行われてアプリケーションが起動します。起動に成功していれば、コンソールに"http://xxxx-yyyy-8888.herokuapp.com deployed to Heroku"といった表示がされていますので、そのURLにアクセスしてみましょう。
$ curl "http://xxxx-yyyy-8888.herokuapp.com/myApp/MyServlet" Hello, Embedded-Glassfish!!
うまく動作しました。続けて何かを試してみる場合は、commitとpushを繰り返していけばプロジェクトの再ビルドとアプリケーションの再起動が行われます。使い終わったらアプリケーションを破棄します。
$ heroku destroy xxxx-yyyy-8888
深くは突っ込んでいないのですが、EJBやCDI、JSFも試してみた限り問題無く動作するようです(JPAは未確認)。ただし、どの程度の規模のアプリケーションまで正常稼働するのかはわかりません。もともとHeroku自体が軽量なWebアプリケーションをスケールして稼働させるものなので、あまり大掛かりなアプリケーションは載せようとしない方が良いとは思います。