rubyの軽いWebアプリケーションフレームワークを試してみた
さくらのレンタルサーバで、CGI+rubyで簡単なアプリケーションを作っているのだけど、ライブラリはrubyに標準で添付している cgi ライブラリを使っている。1画面で完結するアプリなら、cgiライブラリでも良いのだけど、もう少し複雑なアプリを簡単に作るには、フレームワークを使いたいところ。
かといって、DBを使う程でもないアプリには、Ruby on Railsは少し大げさな気がするし、CGIしか使えないさくらだと、結構重そうだ。
というわけで、もう少し軽いWebアプリケーションフレームワークをネット上で探してみた。Camping、Rack、Ramazeがなかなか良さそうだったので、この3つのフレームワークを試してみた。
Camping
http://camping.rubyforge.org/files/README.html
ソースが4kb以下という軽量フレームワーク。
http://code.whytheluckystiff.net/camping/wiki/KeepItToJustOneFile
1つのファイルに、アプリの全ての構成要素(MVC)を入れるというポリシーが面白い。
お決まりのHello Worldアプリはこんな感じ。Controllerだけの単純な構造なので、あまりCampingらしさがないかも。
#!/usr/bin/ruby require 'rubygems' require 'camping' Camping.goes :HelloWorldApp module HelloWorldApp::Controllers class Index < R '/' def get "Hello, World." end end end puts HelloWorld.run
でもソースが短いからと言って、CGI起動が速いわけでない。
「Camping.goes :HelloWorldApp」で、HelloWorldAppクラスの動的評価をしているせいか、今回試したフレームワークの中では、起動が一番遅いようだ。詳細は、後述の起動性能比較を参照のこと。CGIでない環境なら、問題にならないと思うけどね。
Rack
http://rack.rubyforge.org/
正確には、Webアプリケーションフレームワークというより、フレームワークを作るためのライブラリで、Webサーバとフレームワークをつなぐためのものなのだけど、最小限の機能を持ったフレームワークとしても利用できる。
http://rack.rubyforge.org/doc/files/SPEC.html
WebサーバとのI/Fは、メソッド呼出の引数1つ(Hash)と戻り値(Array)のみというシンプルさが良い。
Hello Worldアプリ(CGI用)はこんな感じ。
#!/usr/bin/ruby require 'rubygems' require 'rack' class HelloWorld def call(env) [200, {'Content-Type'=>'text/html'}, ["Hello, World."]] end end Rack::Handler::CGI.run HelloWorld.new
戻り値が、ステータスコードとヘッダ、コンテンツのArrayになっているメソッドになっている。上の例では使っていないけど、引数envはHashになっていて、パスやクエリとかが入っている。Rack::Requestを使えば、リクエストパラメータを簡単に取得する事もできる。
難点は、やはりフレームワークとして使うには機能が不足気味な点か。このまま使っても良いんだけど、結局、自作フレームワークをRackの上に作る事になってしまいそう。
例えば、HTML生成用にErubis等のテンプレートを呼び出す場合、以下のようになるのだけど、Erubisを呼び出すコードをいちいち書かなくて済むように、フレームワークっぽいものを作ることになってしまいそう。
#!/usr/bin/ruby require 'rubygems' require 'rack' require 'erubis' class HelloWorld def call(env) [ 200, {'Content-Type'=>'text/html'}, [Erubis::EscapedEruby.load_file("hello.eruby").evaluate] ] end end Rack::Handler::CGI.run HelloWorld.new
Ramaze
Ramaze • The Web Framework for Rubyists
利用できるテンプレートやORM等の自由度が高いフレームワーク。
公式サイトへ行くと、ドキュメントやサンプルが豊富なのも好印象。
Hello Worldアプリはこんな感じになる。
#!/usr/bin/ruby require 'rubygems' require 'ramaze' class MainController < Ramaze::Controller map '/' def index "Hello, World" end end Ramaze.start :adapter => :cgi
テンプレートを使うのも簡単。メソッド名と同じ名前のテンプレートファイルを view ディレクトリの下に置くだけ。Erubisを使うなら、以下のような view/index.rhtml を置くだけ。
<html><body><%= @content %></body></html>
アプリのソースの方は、以下のようになる。
#!/usr/bin/ruby require 'rubygems' require 'ramaze' class MainController < Ramaze::Controller map '/' engine :Erubis def index @content = "Hello, World" end end Ramaze.start :adapter => :cgi
MainControllerのindexを実行したあと、テンプレート view/index.rhtmlが実行される。このとき、インスタンス変数 @content がテンプレートに渡される。
ちなみにテンプレートエンジンは拡張子で判断するので、「engine :Erubis」は省略可能です。
起動性能比較
CGI起動でも軽いWebアプリケーションフレームワークを探すのが目的だったので、起動性能比較もしてみた。起動性能比較といっても、Hello WorldアプリにHTTPリクエストを100回投げて、その所要時間を比較するという単純なもの。
HTTPリクエストを投げるrubyスクリプトは、benchmarkライブラリを利用し、以下のものを用意した。
require 'benchmark' require 'open-uri' count = 100 count = ARGV[0].to_i if ARGV[0] class Loader def initialize(n = 1, prefix = '') @prefix = prefix @n = n end def load(path) contents = '' @n.times do open(@prefix + path) do |f| contents = f.read end end # puts contents end end loader = Loader.new(count, "http://localhost/~NAT/cgi/") # prefixは環境に応じて適当に書き換える Benchmark.bmbm do |r| r.report("cgi") { loader.load("hello_cgi.cgi") } r.report("cgi+erubis") { loader.load("hello_erubis.cgi") } r.report("rack") { loader.load("hello_rack.cgi") } r.report("rack+erubis") { loader.load("hello_rack_erubis.cgi") } r.report("ramaze") { loader.load("hello_rack_erubis.cgi") } r.report("ramaze+erubis") { loader.load("hello_ramaze_erubis.cgi") } r.report("camping") { loader.load("hello_camping.cgi") } end
Hello Worldアプリは、ここまでフレームワーク毎に紹介したものを使った。
なおcgiを使ったHello Worldアプリは、下記のものを使った。
#!/usr/bin/ruby require 'cgi' cgi = CGI.new cgi.out() do "Hello, World." end
cgi+erubisは下記の通り。
#!/usr/bin/ruby require 'rubygems' require 'cgi' require 'erubis' cgi = CGI.new cgi.out() do Erubis::EscapedEruby.load_file("view/index.rhtml"). evaluate({:content => "Hello, World"}) end
手元のMacBook Pro(CPU:Intel Core 2 Duo 2.2Ghz, メモリ:2GB, OS:Mac OS X 10.5.4)で実行したところ、下記のような結果になった。
user system total real cgi 0.100000 0.030000 0.130000 ( 1.850957) cgi+erubis 0.100000 0.040000 0.140000 ( 10.470253) rack 0.120000 0.050000 0.170000 ( 8.563580) rack+erubis 0.130000 0.050000 0.180000 ( 9.949786) ramaze 0.130000 0.050000 0.180000 ( 9.896040) ramaze+erubis 0.150000 0.070000 0.220000 ( 27.786989) camping 0.150000 0.100000 0.250000 ( 53.961681)
totalの差がそれほどなくとも、realの差が大きい場合があるのだけど、この違いはなんなのだろうか・・・?
realに着目すると、erubisを使う場合だと、rack+erubisが速い。cgiとcgi+erubisの差と、rackとrack+erubisの差を比べると、rackの方が小さいのだけど、この違いもよく分からない。totalに着目すると、cgi+erubisとrack+erubisの速さが逆転するし・・・。
なんとなくだけど、realの結果が一番体感速度に近い気がする。体感的には、cgi+erubis ≒ rack+erubis < (性能2倍の壁) < ramaze+erubis < (性能2倍の壁) < camping という感じ。
分からないことだらけだが、とりあえずtotalとrealのいずれでも、cgiが一番速く、campingが一番遅い、といったところは言える。