2008-12-06

CGI を rackup してみた

Ruby は自分の大好きな言語だが、実は長く運用する Web アプリを Ruby で書いたことはない。cgi.rb の評判はずいぶん前から芳しくないし、決定打となるフレームワークの不在が長く続いたこと、すでに PHP を使っていたことが大きな理由だった。

Rails が登場した。勉強した。「うーん、なんか DBMS とか要らないんだけど、どうしたらいいのよ?」と思っているうちに世間ではすっかり定着、代わりに自分の中では興味は薄れていった。そうこうしているうちに Rails の問題点もちょこちょこ指摘されるようになり、prototype.js とともに先駆者ゆえの苦難を味わっているなぁと感じている今日この頃。

Merb だなんだと言われていた中、Rack が登場した。これだ!と思った。こういうシンプルなやつが欲しかったんだよ! しかしそれから特に何の理由もないまま一年半の月日が流れた。なんかこんなんばっかだな。RSS の登場により情報の収集は速くなったけど、知識にするための時間、手を動かして「つかむ」までの時間は、自分という人間の性能が変わらない1のでちっとも短くならない。

そんなオレ語りはどうでもよく、今回はようやく自分で Rack を使ってみましたよというお話。しかも、あんまり見ない、WEBrick と CGI の二本立て。試したのは gem で入れた rack 0.4.0

Rack の使い方は二段階ある

超簡単なチュートリアルを読んでもリファレンスを見てもこの違いを明確に意識することができなくて苦労した。Rack の機能には以下の二段階がある。

  1. Handler によってアプリケーションサーバの違いを抽象化する
  2. rackup によって便利なミドルウェアを使う

この違いは

  • ruby から直接 Rack::Handler::FOO.run( App.new )
  • rackup から run( App.new )

の違いからくる。要するに

ミドルウェアを use したければ rackup しろ

ってこと。

逆に言うと rackup しなくても Request, Response の抽象化は行えるので基本的なことはできる。

ではそれぞれの方法をもう少し詳しく見てみる。

※ ブコメいただきました。ミドルウェアの中にアプリケーションを突っ込んで、ミドルウェアを Handler に渡すという方法もあるようです。なんかちょっと不思議な感じもしますが、そもそも内部ではミドルウェアでアプリケーションを再帰的に wrap する構造のようです。

lib/rack/lobster.rb at master from chneukirchen's rack — GitHub

ruby から直接 Handler を呼ぶ方法

WEBrick の例

komagata さんの記事から拝借。

ウノウラボ Unoh Labs: RackでWebアプリのWebサーバー依存を無くす

#!/usr/bin/env ruby
require 'rubygems'
require 'rack'

class HelloRack
  def call(env)
    [200, {"Content-Type" => "text/plain"}, ["Hello, Rack"]]
  end
end

Rack::Handler::WEBrick.run HelloRack.new, :Port => 3000

この hello-rack.rb を

ruby hello-rack.rb

で起こし、これにブラウザからアクセスすると

Hello, Rack

と表示される。うむ。

CGI の例

同じことは CGI でも当然できる。

#!/usr/bin/env ruby
require 'rubygems'
require 'rack'

class HelloRack
  def call(env)
    [200, {"Content-Type" => "text/plain"}, ["Hello, Rack"]]
  end
end

Rack::Handler::CGI.run HelloRack.new

こんな感じで Handler を CGI にしてやれば ok. これを

  • Web サーバ配下の CGI の実行可能なディレクトリに置いて
  • 実行権限を付けて

ブラウザからアクセスしてやるとさっきと同じように

Hello, Rack

と表示される。めでたしめでたし。

これで cgi.rb を使わなくても Ruby で CGI を書けるようになったよ!

CGIAlt があるとか言わないでね。)

気をつけなきゃいけないこと

以下は自分のためのメモ

  • CGI の場合は Web サーバがアプリケーションを勝手に起こしてくれるけど、実行権限の付与が必要
  • WEBrick(など)の場合は自分でアプリを起こしてやらないといけない

自分の場合は生 CGI を触る機会が減っているので実行権限の付与をしょっちゅう忘れる。

でも Rack の本当の威力を味わうなら rackup

rackup は

rackup -s webrick [config file]

などとして server(handler) を指定してアプリケーションサーバを起動する。rackup が行われると、中で

Rack::Builder

がアプリケーションサーバの環境を作ってくれ、自分の書いたアプリケーションはこの中で実行される。

ここで初めて use が使えるようになる。

もうちょっと細かく見てみよう。

use は Rack::Builder の instance_method

Rack でミドルウェアを追加する use は Rack::Builder の instance_method となっている。

irb(main):001:0> require 'rack'
=> true
irb(main):002:0> Rack::Builder.methods.grep( /use/ )
=> []
irb(main):003:0> Rack::Builder.instance_methods.grep( /use/ )
=> ["use"]

つまり

require 'rack'

しただけでは use は使えない。

rackup の流れ

rackup を読んでみると、アプリケーションは rackup の中で以下のように実行されている。(詳細は端折ってある)

Rack::Builder.new {
  run inner_app
}

つまりそういうこと。Rack の便利機能を使いたければ rackup すべし。

では実際に rackup してみる。

WEBrick の例

直接起動で use の使えない様子

さきほどの hello-rack.rb に use を加える。

#!/usr/bin/env ruby
require 'rubygems'
require 'rack'

use Rack::ShowExceptions

class HelloRack
  def call(env)
    [200, {"Content-Type" => "text/plain"}, ["Hello, Rack"]]
  end
end

Rack::Handler.WEBrick.run HelloRack.new, :Port => 3000

すると、当然ながら

$ ruby hello-rack.rb
hello-rack.rb:6: undefined method `use' for main:Object (NoMethodError)

怒られる。use なんて使えない。

rackup してみる

#! /usr/bin/env ruby
require 'rubygems'
require 'rack'

use Rack::ShowExceptions

class HelloRack
  def call(env)
    [200, {"Content-Type" => "text/plain"}, ["Hello, Rack"]]
    cho_www
  end
end

run HelloRack.new

ここで注目してほしいのは、

  • cho_www という syntax error をアプリケーションの中に仕込んであるところ
  • run の呼び方を変えたところ

どうも

rackup するときは Handler は外から(rackup の -s オプションで)指定してあげた方がなんかうまくいく感じ。

これを hello-rack.ru の名前で保存し、

rackup -s webrick -p 3000 hello-rack.ru

WEBrick を rackup してブラウザ上で Trace Back

で実行。

ご覧のようにブラウザ上に Exception を表示できる。画像では端折ってあるが TraceBack も表示できる。当然のことながら cho_www を削除すれば普通に動く。

CGI の例

直接起こして use してダメなのはさっき WEBrick で見たので省略。

rackup する

ru ファイルはさっき用意した hello-rack.ru をそのまま利用。CGI の方は以下のようにする。

#! /bin/sh

/opt/local/bin/rackup hello-rack.ru

CGI でも use Rack::ShowExceptions が使えた!

パスが /opt/local/bin なのは MacPorts で入れた Ruby で gem を使ってインストールしているから。rackup のパスは適宜調整すること。

これを hello-rack.cgi の名前で保存、ごにょごにょしてブラウザからアクセスしてみる。画像の中の URL の違いに注目。確かに CGI で動いている。

ただし、当然のことながら CGI で rackup するのは負荷的には不利。

従来の流れ

  1. shebang から Ruby を起動
  2. 自分自身を読み込ませる

rackup 時の流れ

  1. shebang で sh や他のインタプリタから
  2. rackup コマンドを呼び出し
  3. そこから ruby が呼ばれ
  4. アプリケーションがロードされる

という長い道のりを要するようになってしまう。でもメリットでかいし。

わざわざ別プロセスの rackup から起動しなくてもいんじゃね?と思いたいのは分かる。自分も思った。でも結局 rackup と同じとまではいかないまでも似たような流れで Rack::Builder を呼んであげなきゃいけないので、Rack に頼るうまみが薄れちゃうよね?と思うのでそういうことはしないことにした。

まとめ

何はともあれ、Rack の基本的な使い方は分かった。map とかは分かってないけど、これで

  1. WEBrick で開発
  2. とりあえず CGI で deploy
  3. 負荷的にまずいので FastCGI にしましょうか

といった柔軟な運用が可能になった。

よしよし。

参考

  1. というか加齢とともに落ちる一方だぜ! オレは10代の若者じゃねぇ! 

About

例によって個人のなんちゃらです