Google App Engine上でRubyアプリを動かす手順

前置き

Google App Engine(以下GAE)では、公式には現在のところPythonとJavaしかサポートされていませんが、JRubyという素晴らしいプロダクトのおかげでJava VM上でRubyスクリプトを実行できるため、考えようによってはRubyも既にサポート対象になっていると言えなくもありません。

実際にググってみても既に結構な量の情報が存在するのですが、どうもJRuby on Railsを対象とした情報が多く、素のRubyアプリケーションを動かすための情報があまり無いように感じました。

Railsももちろん優れたフレームワークなのですが、ちょっとしたアプリケーションを作るのには少々重過ぎますよね…。

ということで、非Railsな、もっとシンプルなRubyアプリをGAE上で動かすための手順を調べてみました。

ポイント

今回は以下の前提で作業を進めていきます。

環境は、手元にたまたまインストールしたてのものがあったという理由で、CentOS 5.3を選択しました。

Google App Engine for Java環境の構築

GAE上でJRubyを動かすには、当然ながらGAE上でJavaアプリが動作する環境(Google App Engine for Java、以下GAEJ)が必要ですので、まずは以下のページを参考に、Google App Engine SDK for Java(後述)に付いているJavaのデモアプリを動かせる環境を作ります。

GAE上で動くJavaアプリを開発するために最低限必要なものは以下の二つです。

JDKのインストール

以下のページから、最新版のLinux用JDK(jdk-6u14-linux-i586-rpm.bin)をダウンロードし、インストールします。

ダウンロードしたファイルはシェルスクリプトになっていますので、

$ sudo sh jdk-6u14-linux-i586-rpm.bin

というように実行します。利用許諾にyesと答えてしばらく待っているとインストールが完了します。

インストールが終わったら、以下の環境変数を設定しておきます。Bashを使っているのであれば.bashrcあたりに追加しておくと良いです。

export JAVA_HOME=/usr/java/default
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/jre/lib:$JAVA_HOME/lib:$JAVA_HOME/lib/tools.jar

軽くバージョンの確認をしておきます。

$ java -version
java version "1.6.0_14"
Java(TM) SE Runtime Environment (build 1.6.0_14-b08)
Java HotSpot(TM) Client VM (build 14.0-b16, mixed mode, sharing)
$ javac -version
javac 1.6.0_14

問題無さそうです。

Google App Engine SDK for Javaのダウンロード

以下のページからGoogle App Engine SDK for Javaをダウンロードし、適当な場所に展開しておきます。*1

$ wget http://googleappengine.googlecode.com/files/appengine-java-sdk-1.2.1.zip
$ unzip appengine-java-sdk-1.2.1.zip

この時点で、既にローカルでGAEJアプリケーションを動作させる環境は整いました。試しに今展開したSDK同梱のデモアプリを動かしてみます。

$ appengine-java-sdk-1.2.1/bin/dev_appserver.sh --address=0.0.0.0 appengine-java-sdk-1.2.1/demos/guestbook/war
The server is running at http://localhost:8080/

8080ポートでアクセスして、guestbookアプリの画面が表示されればOKです。*2

JRuby on GAEJ環境の準備

無事GAEJ環境が出来ましたので、次はこの上でJRubyを動作させるために必要なものを準備していきます。

GAEJ上でJRubyを動かすのに必要なものは以下の二つです。

jruby-complete.jarの作成

jruby-complete.jarはJRubyの動作のために必要な機能が全て詰め込まれたライブラリです。普通にパッケージからJRubyをインストールした場合は付いてきませんので、ソースから自分でコンパイルする必要があります。

JRubyをソースからコンパイルするためにgitとantが必要になりますので、まずはそれらを用意します。

gitのインストール

以下のページを参考に、yumからパッケージインストールします。

リポジトリを二つ追加します。

$ sudo vim /etc/yum.repos.d/git.repo
[git]
name=Base git repository
baseurl=http://www.kernel.org/pub/software/scm/git/RPMS/$basearch
enabled=1
gpgcheck=0
$ sudo vim /etc/yum.repos.d/rpmforge.repo
[rpmforge]
name = Red Hat Enterprise $releasever - RPMforge.net - dag
mirrorlist = http://apt.sw.be/redhat/el5/en/mirrors-rpmforge
enabled = 0
gpgcheck = 0

以下のコマンドでインストールできます。最新版だと依存性のエラーが出ますので、バージョンを指定しています。

$ sudo yum install git-1.5.6.1-1 --enablerepo=rpmforge
antのダウンロード

JRuby1.1以上ではAnt1.7以上が要求されるため、yumからパッケージインストールしたものは使えません。なので、公式サイトからダウンロードしたものを使います。

適当な場所に展開しておけばOKです。特にインストール作業などは必要ありません。

$ wget http://ftp.riken.jp/net/apache/ant/binaries/apache-ant-1.7.1-bin.tar.gz
$ tar xvzf apache-ant-1.7.1-bin.tar.gz
JRubyソースの取得とコンパイル

ではJRubyのソースをコンパイルし、jruby-complete.jarを作成します。

以下のようにコマンドを実行します。少々時間がかかるかもしれません。

$ git clone git://kenai.com/jruby~main jruby
$ cd jruby
$ ../apache-ant-1.7.1/bin/ant jar-complete

これでjruby/libディレクトリ内にjruby-complete.jarができます。*3

JRuby-Rackのダウンロード

次にJRuby-Rackをダウンロードしますが、その前に、JRuby-Rackがどんな物かを説明しておきます。

GAEJではJava サーブレット標準という仕組みを利用してWebアプリケーションを作成します。GAEJ上でJRubyを動かそうと思った場合、サーブレットとJRubyの橋渡しをする部分のJavaプログラムを自作しなくてはいけません。

しかし、ありがたいことにJRuby-Rackがその面倒な部分を全て請け負ってくれます。

JRuby-Rackというのは、Ruby用に元々存在しているRackというソフトウェアの拡張版です。Rackに関する説明は以下のページが参考になります。

一言で説明すると、WebサーバとWebアプリケーションフレームワークの間に挟まって、各々の非互換性を吸収してくれるミドルウェアです。

JRuby-Rackは、RackがサポートしているWebサーバに加えてJavaサーブレットもサポートしているため、それを利用することで、サーブレットを意識することなく楽にWebアプリケーションが書けるようになる…というわけです。

今回はWebアプリケーションフレームワークを使いませんので、アプリケーションから直接Rackを触ることになります。少々癖がありますが、覚えること自体は少ないですので、先ほどのページや以下のページにざっと目を通して感覚を掴んでおくと良いかもしれません。

ではJRuby-Rackをダウンロードします。特にインストール作業などをする必要はなく、適当な場所に置いておけばOKです。

$ wget http://kenai.com/projects/jruby-rack/downloads/download/jruby-rack-0.9.4.jar

サンプルアプリケーションの作成

さて、ここまででJRuby on GAEJアプリに必要な材料は全て揃いました。とりあえず、簡単なHello worldアプリを作成してみます。

まずはアプリケーションのファイルを格納するディレクトリを作ります。GAEJでは、Javaサーブレット規格で定められた通りの配置でファイルが存在している必要があります。

$ mkdir sample-jruby-on-gaej
$ cd sample-jruby-on-gaej
$ mkdir -p WEB-INF/lib

アプリケーションのトップディレクトリにWEB-INFという名前のディレクトリが存在し、その中にアプリケーションに必要なファイルが全て収まっていなければいけません。

今作ったlibディレクトリには、これまで集めてきたJRuby on GAEJの動作に必要な3つのファイルを格納します。

$ cp ../appengine-java-sdk-1.2.1/lib/user/appengine-api-1.0-sdk-1.2.1.jar WEB-INF/lib
$ cp ../jruby/lib/jruby-complete.jar WEB-INF/lib
$ cp ../jruby-rack-0.9.4.jar WEB-INF/lib

アプリケーション本体は、以下のページを参考に、"Hello world!"というプレーンテキストを出力するだけのシンプルなものを書きます。配置場所はWEB-INFディレクトリ直下です。

$ vim WEB-INF/hello.rb
require 'rubygems'
require 'rack'

class HelloWorld
  def call(env)
    [ 200, { 'Content-Type' => 'text/plain' }, [ 'Hello world!' ] ]
  end
end

設定ファイルの作成

更に、GAEJの動作には二つの設定ファイルが必須となります。以下のページを参考に、最低限必要な設定を書いていきます。

$ vim WEB-INF/appengine-web.xml
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <application>sample-jruby-on-gaej</application>
  <version>1</version>
  <system-properties>
    <property name="jruby.management.enabled" value="false" />
    <property name="os.arch" value="" />
  </system-properties>
</appengine-web-app>
$ vim WEB-INF/web.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
  <context-param>
    <param-name>public.root</param-name>
    <param-value>/</param-value>
  </context-param>

  <context-param>
    <param-name>rackup</param-name>
    <param-value>
      require 'hello.rb'
      run HelloWorld.new
    </param-value>
  </context-param>

  <filter>
    <filter-name>RackFilter</filter-name>
    <filter-class>org.jruby.rack.RackFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>RackFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <listener>
    <listener-class>org.jruby.rack.RackServletContextListener</listener-class>
  </listener>
</web-app>

サンプルアプリケーションの動作確認

ここまででアプリケーションの動作に必要なファイルは一通り揃いました。

試しにローカルで起動して、きちんと動くことを確かめてみます。

$ ../appengine-java-sdk-1.2.1/bin/dev_appserver.sh --address=0.0.0.0 .
The server is running at http://localhost:8080/

dev_appserver.shの最後の引数にはアプリケーションのトップディレクトリ(WEB-INFの親ディレクトリ)を指定します。

8080番ポートでアクセスして"Hello world!"が表示されたら成功です。

ついでですので、先ほどのhello.rbを色々書き換えてみて、確かにRubyスクリプトが動いているのだということを確認してみます。*4

require 'rubygems'
require 'rack'

class HelloWorld
  include Rack::Utils   # Rack::Utils.escape_htmlを簡単に使うため

  def call(env)
    req = Rack::Request.new(env)
    res = Rack::Response.new

    res.write '<html><head><title>Sample of JRuby on GAEJ</title></head><body>'

    # いくつか基本的な情報を表示
    res.write '<h2>Ruby version</h2>'
    res.write "<p>#{RUBY_VERSION}</p>"

    res.write '<h2>Platform</h2>'
    res.write "<p>#{RUBY_PLATFORM}</p>"

    res.write '<h2>Load paths</h2>'
    res.write '<ul>'
    res.write $:.map { |path| "<li>#{escape_html(path)}</li>" }.join
    res.write '</ul>'

    # POSTパラメータの一覧を表示
    res.write '<h2>Posted parameters</h2>'
    res.write '<dl>'
    req.params.each_pair do |key, value|
      res.write "<dt>#{escape_html(key)}</dt><dd>#{escape_html(value)}</dd>"
    end
    res.write '</dl>'

    # 環境変数の一覧を表示
    res.write '<h2>Environment variables</h2>'
    res.write '<dl>'
    req.env.each_pair do |key, value|
      res.write "<dt>#{escape_html(key)}</dt><dd>#{escape_html(value)}</dd>"
    end
    res.write '</dl>'

    res.write '</body></html>'
    res.finish
  end
end

今のところRubyスクリプトを書き換えたらサーバを再起動する必要がある?ようですので、先ほど立ち上げたdev_appserver.shをCtrl+Cで一旦終了し、再度実行します。

同じようにローカルの8080番にアクセスしてみると、色々と情報が列挙されるはずです。

アプリケーションのアップロード

あとはこのままこのアプリケーションをGAE上にアップロードすれば、世界中からアクセス可能なWebアプリの完成です。

当然GAEのアカウントを取得して、かつGAEJが使用可能な状態になっていなければなりません。*5

アップロードは以下のコマンドを実行するだけです。*6途中でGoogleアカウントとパスワードを聞かれてきますので入力します。

$ ../appengine-java-sdk-1.2.1/bin/appcfg.sh --enable_jar_splitting update .

最後の引数には先ほどと同様にアプリケーションのトップディレクトリを指定します。

GAEのダッシュボードで正しくアップロードされていることを確認し、表示されているURLにアクセスしてみます。先ほどのローカルでのテストと比較して、$LOAD_PATHなどの値が異なっていることが見てとれると思います。

JRuby on GAEJアプリのテンプレート

ここまででJRuby on GAEJアプリの作り方は一通り完了です。思ったよりも作業量が多くて疲れてしまいますね…。

しかし、よくよく手順を見返してみると、大変だったのは主に必要なライブラリの準備でしたし、またアプリケーション自体も本体のファイル(今回の場合は"hello.rb")以外はほぼ定型であり、そのまま使いまわすことができそうです。

ということで、JRuby on GAEJアプリの雛形として使えるパッケージを作成しました。

といっても、上のHello worldアプリをtarボールにしただけです。JRubyやJRuby-Rackは同梱されていますので改めて準備する必要はありませんが、JDKとGoogle App Engine SDK for Javaは別途インストールしておいてください。

また、将来的にGAEやSDKのアップデートによって使えなくなる可能性もありますので、その点もご了承下さい。

使い方としては、

  1. 展開して
  2. WEB-INF/hello.rbを好きなように書き換えて
  3. WEB-INF/appengine-web.xmlのapplicationタグとversionタグの値を適切に設定して
  4. GAEへアップロード(またはローカルでテスト起動)

するだけです。

ごく簡単なWebアプリなら、これでとりあえずは書けるかも?

もう少し本格的なアプリケーションの作成

とは言え、現状のままではできることも貧弱です。

JRuby on GAEJアプリは、GAEのサンドボックス内で動作する関係上、様々な機能的制限をかけられています。

例えばファイルへのアクセスも禁止されていますし、net/httpも使えませんのでHTTP経由で他のリソースを取得することもままなりません。

しかし、それらの制限された機能を補うために、GAE独自のインターフェースが用意されていますので、これを使うことでパワフルなWebアプリケーションを作成することも可能です。

GAEJで提供されているのは当然Javaへのインターフェースとなりますが、JRubyからそれらを使えるようにしてくれるラッパーライブラリもいくつか作成されています。

以下のページによると、DBへのアクセス、アカウント情報の取得、画像処理、メールサービスの利用、URLフェッチなど、めぼしい機能は一通りJRubyからも利用できるようです。

次回*7は、これらのライブラリを利用して、JRuby on GAEJ上で動作する簡単なTwitter BOT作りに挑戦してみようと思います。*8

*1:英語版のほうがバージョンが新しい場合がありますので、一度英語版のページをチェックしたほうが手間がかからなくて済みます。

*2:端末と開発環境が同一筐体の場合は"--address=0.0.0.0"オプションは不要です。

*3:少し古い資料では、このあと更にライブラリを分割する手順が説明されていますが、現在のGAEではその作業は必要ありません。

*4:Rack::Response#finishは本来ブロックを使った書き方もできるのですが、何故か手元の環境ではブロックを渡して使うと正しく本文データを返してくれないので、仕方なく少々格好悪い書き方をしています。

*5:その辺はばっさり省略します。

*6:この--enable_jar_splittingオプションのおかげで、jruby-complete.jarファイルの分割が不要になっているようです。

*7:もしくは近い未来…あるいは遠い未来

*8:Webアプリじゃない?こまけぇこたぁ(略)