« Back to Home

Getting Started with Glassfish on Heroku

新年明けましておめでとうございます。今年もよろしくお願いいたします。私は今回の年末年始のお休みの間、なるだけ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上で実行する

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アプリケーションをスケールして稼働させるものなので、あまり大掛かりなアプリケーションは載せようとしない方が良いとは思います。