at posts/single.html

10分ではじめる GAE/JRuby (OAuth + Sinatraのサンプル)

ちょっと時間ができたので、 Google App Engine (GAE) 上で JRuby を動かしてみよう。 前に読んだ情報だと、 GAE 上で JRuby を動かすためには JRuby をコンパイルしたり WAR に固めてアップロードしたりと、かなり面倒な印象があった。 でも今は、ビックリするくらいカンタンに動かすことができるようになっている。 GAE 上で Java を動かすよりお手軽なんじゃないかな。

とりあえずのゴールは、ちょっと前に作ったSinatra と OAuth を使って Twitter のタイムラインを取得を動かすところまで。 所要時間は10分で。

GAE のアカウント取得 (2分)

Google App Engineのサイトで、 GAE のアカウントを取得する。 すでに Google アカウントを持っていれば、携帯メールのアドレスで認証するだけ。 携帯を持っていない人はどうしているんだろう…。 アカウントが取得できれば、 Python 版も Java 版も使えるようになっている。 前は Java 版は先着10,000様のみって書いてあったけど、今は特に申請不要みたい。

アプリケーション id の登録 (1分)

Create an Application で新たにアプリケーション id を登録する。 ここで登録した id が *.appspot.com のサブドメイン名になる。 今回はテストなので machu-jruby とした。

現時点では合計10個までのアプリケーション id が登録できる。 一度登録した id は削除できないみたいなので、慎重に。

《参考》 Java サンプルの動作 (2分)

GAE/JRuby を動かすためにこの手順は不要なんだけど、参考までに。 興味がなければ読み飛ばしてもらって OK 。 まず、ローカル環境の JDK のバージョンを確認する。 環境は Mac OS X 10.6 (Snow Leopard) 。

$ java -version
java version "1.6.0_15"
Java(TM) SE Runtime Environment (build 1.6.0_15-b03-219)
Java HotSpot(TM) 64-Bit Server VM (build 14.1-b02-90, mixed mode)

$ javac -version
javac 1.6.0_15

次に、 Google App Engine SDK for Java をダウンロードして、サンプルを動かしてみる。

$ unzip appengine-java-sdk-1.2.2.zip
$ cd appengine-java-sdk-1.2.2
$ bin/dev_appserver.sh demos/guestbook/war

これでローカルのポート 8080 に、ゲストブックのサンプルが起動する。 demos/guestbook/war にはコンパイル済みの class ファイルが生成されているけど、自分でアプリを作る場合には当然コンパイルしておかなければいけない。

アプリケーションの GAE へのアップロードは試していないけど、アプリケーションのアップロード - Google App Engineの手順を参考にして、 appengine-web.xml を作成し (さっき取得したアプリケーションidを書く) 、「bin/appcfg.sh update demos/guestbook/war」を実行すればよさそう。

実際にはコマンドラインで頑張るよりも、 Google Plugin for Eclipse を使って Eclipse 上で開発することになると思う。

GAE/JRuby を動かす (2分)

GAE/J の概要が分かったところで、いよいよ GAE/JRuby を動かす。 基本的な手順は、appengine-jrubyで簡単GAE/JRuby開発 - しばそんノートのとおり。

少し前のエントリで、GAE/JRuby上でRackアプリを動かす手順を書きました。

このときの手順は、Java SDKをインストールして、GAE SDKをダウンロードして、jruby-complete.jarをコンパイルして…と、やや煩雑なものでした。この当時はおそらくこれが一般的な手法だったのではないかと思います。

しかし、今や時代は変わりました。今GAE/JRubyでRackアプリを作るのに必要な作業は、たったひとつのgemのインストールだけです。

その gem とは、appengine-jruby 。 普段使っている ruby の環境 (JRubyではなくCRuby) で、 google-appengine をインストールする。

$ sudo gem install google-appengine

準備はこれだけ。 Java の時にダウンロードしていた Google App Engine SDK も、この gem を入れれば自動的にダウンロードされる。

まずは appengine-jruby の GettingStarted に書かれている Hello World アプリを作る。 と言っても、以下の内容で config.ru を作るだけ。

require 'appengine-rack'
AppEngine::Rack.configure_app(
    :application => "application-id",
    :version => 1)

run lambda { Rack::Response.new("Hello World!") }

config.ru は Rack の設定ファイル。普通の Rack と違うのは、 appengine-rack を呼んでいることと、 AppEngine::Rack.configure_app で GAE 用の設定をしていることだけ。この設定は、 Java 版の appengine-web.xml 相当かな。 "application-id" は、最初に取得したアプリケーション id に書き換えること。

dev_appserver.rb コマンドを使ってサンプルを起動する。 Java 版と同じようにポート 8080 で立ち上がる。

$ dev_appserver.rb .

appcfg.rb で GAE にアップロードする。

$ appcfg.rb update .

これらのコマンドも、 Java 版の dev_appserver.sh と appcfg.sh にあわせているっぽい。 コンパイルが不要なので、Java版よりお手軽。

GAE で Sinatra を動かす (2分)

ここまでで GAE/JRuby 上で Rack が動いた。 次は Rack の上に Sinatra さんを載せてみる。 この手順も、さっきと同じで appengine-jrubyで簡単GAE/JRuby開発 - しばそんノートappengine-jruby の GettingStarted を参考にした。

JRuby と Rack は最初にインストールした google-appengine の gem パッケージに含まれているけど、 Sinatra は自分でインストールしなければいけない。 GAE にアップロードするときには Sinatra のパッケージも war に含めるので、 /opt/local/lib/ruby/gems ではなく、これから作成するアプリと同じディレクトリにインストールする必要がある。 gem パッケージのインストールには、 appcfg.rb gem コマンドを使う。

appcfg.rb gem install sinatra

コマンドを実行すると、カレントに .gems ディレクトリが生成される。 パッケージは、この .gems ディレクトリに展開される。 逆に言うと、 .gems ディレクトリにインストールされていない追加パッケージは、 GAE にアップロードしたときに見えなくなる。

あとは、適当に Sinatra アプリを生成し、 config.ru を書き換えるだけ。普通に Sinatra を動かすのと変わらない。

GAE/JRuby で Sinatra + OAuth を動かす (5分)

ただの Hello World アプリを起動するだけでは、 GAE を使っている特徴が見えてこない。 そこで、前に作った Sinatra と OAuth を使って Twitter のタイムラインを取得するアプリを動かしてみる。 GAE だとローカルファイルへの IO や Fetch 以外での外部通信が禁じられているので、この辺が修正箇所になるかな。

まずは、 appcfg.rb コマンドを使って Sinatra に加えて oauth と twitter の gem をインストールする。

$ appcfg.rb gem install oauth twitter

ローカルにてサーバを起動する。

$ dev_appserver.rb

予想どおり、 Consumer (このアプリ) から Service Provider (twitter) へ通信するところでエラーになる。 OAuth ライブラリは Net::HTTP を使って外部と通信している。 以下、OAuthコンシューマの仕組みと実装 〜 Ruby編 - しばそんノートより引用。

「JRuby for GAE/JでもNet::HTTPが使えるようになる」というrb-gae-supportと組み合わせればOKなのかもしれませんが*1、OAuthの仕様自体はシンプルなものですし、せっかくなので勉強がてら自分で実装してみることにします。

rb-gae-support は GAE が提供する機能を Ruby から簡単に使えるようにするためのライブラリ。 Net::HTTP 互換機能の他にも、 GAE が提供する認証機能を使うための GAE::User や、データを保存するための GAE::Memcache がある。 という訳で、インストール。

$ appcfg.rb gem install rb-gae-support

config.ru にてこのライブラリを require したら、今度はアプリのトップページからエラーになってしまった。 ここでさんざん悩んだんだけど、どうやらこの前に「jruby-openssl」をインストールしていたことが原因だった。 rb-gae-support の README に、 jruby-openssl とは一緒に使えないよっていう記述がある。

do not use jruby-openssl in your app engine application - it’ll break it!. It uses native code, which is not supported on app engine.

仕方がないので、 OpenSSL が提供する機能を使うのは諦めて、 jruby-openssl をインストールしたら動くようになった。

最終的に、 config.ru は以下のようになった。

require 'rubygems'
require 'appengine-rack'
require 'rb-gae-support'
require 'oauth-sample-gae'

AppEngine::Rack.configure_app(
        :application => 'machu-jruby',
        :version => 1
)

run Sinatra::Application

前に作ったローカル版の OAuth + Sinatra アプリと GAE 対応版の差分は以下の通り。 ソースコードは Gist:180755 に載せている。

--- /Users/machu/work/gisty/170131/oauth-sample.rb      2009-08-19 15:10:31.000000000 +0900
+++ oauth-sample-gae.rb 2009-09-04 09:14:50.000000000 +0900@@ -9,7 +9,7 @@
 end
 
 configure do
-  use Rack::Session::Cookie, :secret => Digest::SHA1.hexdigest(rand.to_s)
+  use Rack::Session::Cookie
   KEY = "twitter_consumer_key"
   SECRET = "twitter_consumer_secret"
 end

jruby-openssl ライブラリと rb-gae-support が競合しているため、 Rack の :secret オプションが使えないのが大きな課題(つまり、セッションの中身をクライアントが自由に書き換えられる状態)。 まぁ、 GAE を使うのなら、 GAE 側にセッション情報を持たせる方向がいいよね。

次の一歩 (Priceless)

GAE を使うならデータストアは避けて通れない。 これも以前なら Java の API を叩いていたみたいだけど、今なら appengine-jruby ライブラリから呼び出す方が楽みたい。 DatastoreOverview を一読するのがよさそう。 以下のようなサンプルコードが書かれている。

require 'appengine-apis/datastore'

e = AppEngine::Datastore::Entity.new('Employee')
e[:name] = 'Fred'
e[:role] = 'Manager'
e['hire_date'] = Time.now

AppEngine::Datastore.put e

続きは以下の日記にて。

参考

まとめ

  • appengine-jruby を使うことで、お手軽に GAE/JRuby を試す環境が作れるようになっている。オススメ!
    • Rack ベースが基本。 Rack 上で Sinatra も簡単に動く。 Rails も動くらしい。
    • 本格的なアプリを作ろうとすると無料のリソース枠では厳しいから、 Sinatra で作れるくらいの小さなアプリを作る場合の方が使えそう。
    • Java -> JRuby -> appengine-jruby と、複数レイヤの上なのでデバッグが大変そう。本格アプリなら Java のほうがいい?
  • GAE だと IO (ローカルファイルの読み書き) に制約。
    • rb-gae-support で Net::HTTP が使えるようになるが、 jruby-openssl が使えなくなる副作用。
    • その結果、 Rack::Session::Cookie の :secret が指定できなくなるのは痛い。
  • 10分って書いたのに14分かかってる。(もちろん、実際には試行錯誤しながら数時間かかってる)

Ruby は書けるけど Python を覚えるのはちょっと大変。Java はコンパイルが面倒だし…という人には、いまが GAE で遊んでみるちょうどいいタイミングだと思う。

関連する日記