Hatena::ブログ(Diary)

NAT’s Programming Champloo このページをアンテナに追加 RSSフィード

2008-08-17

[] rubyの軽いWebアプリケーションフレームワークを試してみた

さくらレンタルサーバで、CGI+rubyで簡単なアプリケーションを作っているのだけど、ライブラリrubyに標準で添付している cgi ライブラリを使っている。1画面で完結するアプリなら、cgiライブラリでも良いのだけど、もう少し複雑なアプリを簡単に作るには、フレームワークを使いたいところ。

かといって、DBを使う程でもないアプリには、Ruby on Railsは少し大げさな気がするし、CGIしか使えないさくらだと、結構重そうだ。

というわけで、もう少し軽いWebアプリケーションフレームワークをネット上で探してみた。Camping、RackRamazeがなかなか良さそうだったので、この3つのフレームワークを試してみた。

Camping

404 Not Found

ソースが4kb以下という軽量フレームワーク

no title

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

Rack: a Ruby Webserver Interface

正確には、Webアプリケーションフレームワークというより、フレームワークを作るためのライブラリで、Webサーバフレームワークをつなぐためのものなのだけど、最小限の機能を持ったフレームワークとしても利用できる。

File: SPEC

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が速い。cgicgi+erubisの差と、rackrack+erubisの差を比べると、rackの方が小さいのだけど、この違いもよく分からない。totalに着目すると、cgi+erubisとrack+erubisの速さが逆転するし・・・。

なんとなくだけど、realの結果が一番体感速度に近い気がする。体感的には、cgi+erubis ≒ rack+erubis < (性能2倍の壁) < ramaze+erubis < (性能2倍の壁) < camping という感じ。

分からないことだらけだが、とりあえずtotalとrealのいずれでも、cgiが一番速く、campingが一番遅い、といったところは言える。



私的総合評価

フレームワークの評価は、色んな側面から評価すべきだと思うので、簡単にどっちが良いとは言えないのだけど、下記に示すような個人的な好みと、私のやりたいことと照らし合わせると、ramazeが良さそう。

というわけで、しばらくramazeを使ってみようかと思う。

tobytoby 2008/09/22 22:41 こんにちは。
Rubyでお手軽にCGIを使いたい、でもフレームワークの恩恵を受けたい、なんていう時がやはりありますよね……。
ベンチマークも参考になりました。
よい記事をありがとうございます。

NATNAT 2008/09/23 00:47 コメントありがとうございます。
画面が1つ2つならCGIでも良いんですが、数画面くらいになるとコードが複雑になるので、フレームワークが欲しくなります。かといってRailsだとちょっと重いので、ここで紹介したような軽いフレームワークがちょうど良いですね。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証