夏原稿2001 〜 mod_ruby入門?

著者: \ay, 技術監修: 前田修吾

前置き

前号の記事が好評をいただいたに違いないという推測のもとに、 今回もRubyネタをくりひろげてみたい。

さて、まず今回の言い訳だが、 この記事はタイトルからもわかる通り前回の続編―ではない。 前回書いた記事の続きは、その後の展開を妄想してはいるので いずれどこかで披露できるかもしれないが、よくわからない。 なんらかの形で日の目を見せたいと考えているので、 ナニできそうなアレがあったら連絡してほしい。

では本編。

mod_rubyってなんだ

mod_perlのRuby版。

mod_rubyというのは、本質的にはApacheサーバに Rubyのインタプリタを組み込んでしまおうというシロモノである。 まずはここを見よ。

CGIスクリプトを効率的に実行したいというような文脈で とりあげられることが多いようだが、 その効用は必ずしもCGIに限定されたものではない。 Apacheサーバ環境下において、 CGIスクリプトよりもはるかに柔軟に「Rubyする」ことができるようになる という性質を持っており、 CGIスクリプトの効率的な実行などは その性質の持つ側面の一つであると言えよう。

なにがうれしい?

だが、mod_rubyによって現実的に何がうれしくなるのか という点に話が及ぶとなると、 やはりCGIスクリプトの話を挙げなければならないだろう。

これはよく語られる話なのだ。 すなわちmod_rubyを介してCGIスクリプト的なことを実現すると、 いちいちRubyインタプリタを起動しなくてすむようになる。 これはつまりrubyコマンドを起動しなくてすむようになるということだから、 CGIスクリプトと比較するとコスト面で大いに優勢になる。 実際、簡単なRubyスクリプトをCGIスクリプトとして実行した場合と mod_rubyから実行した場合でのベンチマークをとってみたところ、 このような結果が出ている。

CGIスクリプトとmod_rubyの比較
  CGI mod_ruby
並列度 処理時間
[秒]
リクエスト/秒 転送レート
[バイト/秒]
処理時間
[秒]
リクエスト/秒 転送レート
[バイト/秒]
1 52.01 9.61 759 22.01 22.71 1816
2 50.73 9.86 778 22.73 21.99 1759
5 51.52 9.71 767 22.66 22.06 1767
10 52.25 9.57 757 23.24 21.51 1730

あくまで一例ではあるが、大きな差が認められることがわかるだろう。

CGIスクリプトとの違い

これらの差はApacheサーバ自身にRubyインタプリタを内蔵してしまうことによって、 Rubyインタプリタ自体を複数の処理で使いまわすことに由来している。 したがって、この「優勢」には副作用がある。 一般に副作用はRubyインタプリタ全体の状態に関して生ずるが、 それはたとえばこんなものだ。

必ずしもrequireが働くとは限らない。

requireされるファイルの中で 動的な初期化をしているとハマる原因となる。 まあ、それがライブラリであった場合には、 そんなことをするライブラリが悪いと言えよう。 ライブラリでない場合(ある種のデータとか)には requireの代りにloadを使うとよいだろう。

$$が複数の処理で同じになる可能性がある。

一時ファイルやロックにおいて、 この値がユニークであることを期待している場合には 正常に動作しなくなってしまうだろう。

$SAFE

mod_rubyではデフォルトで $SAFEの値を1にしている。 このため、一部のメソッド(File#openなど)に 外部から入力された値をそのまま渡すと SecurityErrorが起こることがある。

グローバル変数は共有される(長生きする)。

共有されると言っても、それはApacheサーバの各プロセス内での話である。 ご存知のようにApacheサーバは複数プロセスから構成され、 各プロセスはあるタイミングでその一生を終えるのだがから、 この共有を積極的に利用することにはほとんど望みがない。 結局、グローバル変数を使ってはならない というオキテ*1はここでも生きてくるわけだ。

一方でCGIスクリプトと違いのないところもある。

CGIサポート

mod_rubyにはCGIライクなインタフェースが用意されているため、 CGIスクリプトは多くの場合に変更なしか、 わずかに変更するだけで動作可能となる。

このことは互換であると同時に相異点でもある。 すなわちmod_rubyによってCGIスクリプトを実行することもできるが、 mod_rubyにおいて実行され得るのはCGIスクリプトだけではないわけで、 これはmod_rubyの利点の一つとして重要だ。

スクリプト間の独立性

グローバル変数やインタプリタの状態が共有されるのに対して、 スクリプトそのものはCGIスクリプトと同じように互いに独立したままである。 mod_rubyではリクエスト毎に一つのスレッドを作り、 その中でスクリプトを実行する。 そのためスクリプト間の独立性そのものは保たれている。

違いはあるようでないようでやっぱりある。 だが、これらの違いというのはギョーギの良いスクリプトであれば、 そう問題になることがないようなものではなかろうか。

ところで、やや余談となるのだが、スクリプトから参照できる 環境変数GATEWAY_INTERFACEの値にも両者で違いがあったりする。 CGIスクリプトとして実行されると GATEWAY_INTERFACEの値が"CGI/1.1"などとなるのに対し、 mod_rubyを介して実行されると"CGI-Ruby/1.1"のようになるのである。 これによって両者を区別することが可能なわけだ。

さあ、使ってみよう

使うにはインストールが必要であるが、 ここはひとつ、こういうことでかんべん願いたい。

apt-get install libapache-mod-ruby liberuby

ただしこれはDebian GNU/Linux(unstable)の場合で、 Vine Linux(VineSeed)の場合にはこのようにする。

apt-get install mod_ruby liberuby

なお、Debian(potato)な環境では 以下のapt-lineをsources.listに加えておくとよいだろう。

deb http://deb.ruby-lang.org/debian potato main contrib non-free

ここで注意が必要なのは二点。 まず、mod_rubyにerubyは必ずしも要らなくなったことと、 それにともないmod_ruby、erubyrubyのバージョンが それぞれ0.8.5、0.9.5、1.6.4より新しくなくてはならない*2ことである。 ただし、mod_rubyからerubyの機能を使いたいとき(後述)には erubyをインストールしておかなくてはならないことは言うまでもない。

とりあえずの設定

Debianなら/usr/share/doc/libapache-mod-ruby/examplesの下の、 VineSeedなら/usr/doc/mod_ruby/examplesの下の、 httpd.confを見てもらいたい。 基本的にはこのサンプルに準じた記述をhttpd.confにしてやればよい。 むろん各種パスは環境に合わせておこう。

詳細は後述するが、この記述によって /ruby/以下の全ファイルと 拡張子rbxであるファイルがCGIスクリプト的に扱われるようになる。 ここでCGIスクリプト的というのは、 スクリプト自体の実行環境全般についてを指している。 よって、スクリプト自体に実行属性がなければならないことと、 OptionsExecCGIが入っていなければならないことに 注意してほしい*3

ちなみにサンプルではコメントアウトされている 二つの部分―for Apache::ERubyRunおよび for Apache::ERbRunとある部分は それぞれerubyまたはERbを使って eRubyテキストを扱うための設定例となる。 mod_rubyとeRubyの関係についても後述するので 適宜参照されたい。

それでははじめよう。

CGIスクリプト→mod_ruby (その一)

CGIスクリプトとして動作した実績のあるスクリプトは 基本的にmod_ruby上で動作させることも可能である。

たとえば先のベンチマークで使ったCGIスクリプトは以下のようなものであった。

#!/usr/bin/ruby
require 'cgi'

c = CGI.new('html4Tr')

c.out do
  c.html {
    c.head { c.title{'sample1'} } + c.body { 
      c.p {'GATEWAY_INTERFACE: ' + (c.gateway_interface || '(null)')} + 
        c.br + c.ul {
        c.params.collect{|k, v| 
          c.li {CGI::escapeHTML(k) + ': ' + CGI::escapeHTML(v.inspect)}
        }
      }
    }
  }
end

とてもシンプルなこのスクリプトは、 実はまったく変更することなくmod_ruby上でも動いてしまう。 もちろん、CGIスクリプトとして動くのに加えて mod_ruby上ででも動いてしまうのはスクリプトがシンプルだからではない。 どちらでも動作可能であるのはcgi.rbを使っているからである。

すでに述べた通り、 mod_ruby上のCGIサポートは純粋なCGIスクリプトと互換性があるが それと同時に違いもある。 cgi.rbはその差を吸収してくれているのである。

CGIスクリプト→mod_ruby (その二)

はたしてcgi.rbが吸収してくれる違いとはどれのことか?

両者の違いについてはいくつか挙げてきたが、 これまでに挙げていない違いがもう一つある。 それはmod_ruby上でCGIスクリプトが実行されるときには NPHスクリプトとして実行されるということだ。 つまり、次のスクリプトはCGIスクリプトとしは動作するが mod_ruby上では動作しない―いや、動作そのものはしているのだが、 しかしそれはApacheサーバ上においてエラーとなってしまうのである。

#!/usr/bin/ruby
print "Content-Type: text/plain\r\n\r\n"
print "Hello World"

そう、NPHであるがゆえにステータスラインについても 面倒を見てやらなくてはならないわけだ。

#!/usr/bin/ruby
print "HTTP/1.1 200 OK\r\n"
print "Content-Type: text/plain\r\n\r\n"
print "Hello World"

こうしておこう。

関連ディレクティブ

ここで、設定について少しだけ触れておこう。

まずSetHandlerだが、 これは指定されたエリアに対して指定されたハンドラ(ここでは ruby-objectという名前のハンドラ)を割り当てるというもので、 特にmod_ruby独自のものではない。 Apacheサーバ一般に使用されるものである。

mod_rubyに特有なのはruby-objectというハンドラだ。 これがmod_rubyそのものであると言ってもよいだろう。 実は、古いmod_rubyにおいては「CGIスクリプト的な動作をするハンドラ」 というものが存在していたのだが、現在はそのようなものはない。 代わりにRubyHandlerというディレクティブを導入し、 RubyHandlerで指定したオブジェクトに リクエストを渡してやるという形態がとられるようになった。

<Location /ruby>
  SetHandler ruby-object
  RubyHandler Apache::RubyRun.instance
</Location>

この記述は、したがって、(1)/ruby以下のファイルに ruby-objectハンドラを割り当て、(2)具体的な処理は Apache::RubyRunクラスのオブジェクトによって行うということを意味している。 実際にはRubyHandlerディレクティブの引数は そのままRubyインタフェースに渡されていて、 このケースで言えば、Apache::RubyRunがSingletonであるために Apache::RubyRun.instanceによってインスタンスが得られるというわけだ。

このように、クラスではなく必ずオブジェクトそのもの(を得られるような内容)を 指定しなくてはならないことに注意してほしい。

ところで、Apache::RubyRunはどこからやってくるのだろうか。 ruby-objectハンドラに組み込まれているのか―というと、それは違う。 以下がその答だ。

RubyRequire apache/ruby-run

RubyRequireディレクティブは Rubyインタプリタにおいてrequireを実行させるためのものである。 ここではapache/ruby-runというライブラリをrequireする。 そう、Apache::RubyRunの定義はapache/ruby-runにおいて なされているわけだ。

こ難しい話はいったんやめにして、もう少し楽しもう。

さあ、もっと使ってみよう

mod_rubyなんて言ってみたところで、 CGIスクリプト的に動かすぶんにはたいした違いがないことがわかっただろう。 せいぜいステータスラインを付けてやるだけだ。

そう思うことができただろうか。

実践リダイレクト

ところが、意外とハマるのがリダイレクトだったりもする。

リダイレクトするためのCGIスクリプトというのは こんなようなものだろう。

#!/usr/bin/ruby
print "Location: http://yendot.org/\r\n\r\n"

ところが、同じものをmod_ruby上で実行してもうまくない。 なんとなく動いてしまう場合もあったりするからタチが悪いのだが、 一般に動かないと見るべきである。 それというのもステータスラインがないからだ。

正しくリダイレクトするためには ステータス302を返してやらねばならない。 したがってNPHである場合の正しいスクリプトはこのようになる。 NPHでないCGIスクリプトにおけるApacheサーバのありがたみが実感できる瞬間だ。

#!/usr/bin/ruby
print "HTTP/1.0 302 moge\r\n"
print "Location: http://yendot.org/\r\n\r\n"

しかしこれでは美しさにややかけるような気がしてくる。 もう少しなんとかならないものか?

実はもう少しならなんとかなる。 たとえばこうだ。

#!/usr/bin/ruby
Apache.request.headers_out['Location'] = 'http://yendot.org/'
exit(Apache::HTTP_MOVED_TEMPORARILY)

このようにexitを使うわけだ。 mod_ruby上のスクリプトでexitすると、 その引数によってステータスラインが自動で生成される。 これは便利… かもしれない。 さらに言うと、このようなやり方もある。

#!/usr/bin/ruby
r = Apache.request
r.status_line = "301 Moved Permanently"
r.headers_out["Location"] = "http://yendot.org/"
r.send_http_header

r.status_line =でステータスラインを設定し、 その上でr.send_http_headerによってヘッダを出力している。 どちらのやり方にしても「Apache.requestってなんだ!?」と思われただろう。 そりゃ、そうだ。しかし、これについては後述とさせていただこう。

後述。後述。後述ばかりだが今しばらく待たれたい。 ここでは「なんとなく」わかってもらえればひとまず十分である。

認証にまつわるエトセトラ

CGIスクリプトでは逆立ちしてもできないことをやってみよう。 それは認証だ。

ここで言う認証はHTTP上で定義されているそれで、 FORMでのやり取りによるもののことではない。 実はHTTP認証に必要となる情報はCGIスクリプトには開放されていない。 よって、一般に行われる.htaccesshtpasswdによるのではなく、 Rubyスクリプトによって認証を行うということは mod_rubyでしかできない芸当なのである*4。 これもまた(先に述べなかった)CGIスクリプトとの相違点の一つだ。

実際にはどうするのかというと、まず、 このような設定が必要となる。

RubyRequire apache/myauth
<Location /secret>
  RubyAuthenHandler MyAuthen.instance
  RubyAuthzHandler  MyAuthz.instance
  AuthType Basic
  AuthName "my secrets..."
  require valid-user
</Location>

ここでAuthTypeAuthNamerequireについては .htaccesshtpasswdでの認証を行う場合のそれと同義である と考えてもらってさしつかえない。 RubyAuthenHandlerRubyAuthzHandlerは―ピンときただろうか。 そう、実際の認証を司るオブジェクトを指定するためのディレクティブで、 mod_rubyによってもたらされたものだ。 ここで指定しているMyAuthenMyAuthzapache/myauthにて定義されているものと考えてほしい。 ちなみにそれはこんなコードになる。

require 'singleton'

class MyAuthen
  # ユーザ名・パスワードの組み合わせの確認をするためのクラス

  include Singleton

  PASSWD = {"foo" => "8dJDGwv.DkKQA"}

  def authenticate(r)
    pass = r.get_basic_auth_pw # 入力されたパスワードを取り出す
    user = r.connection.user   # r.get_basic_auth_pwすると設定される
    if user && PASSWD.include?(user) &&
        pass.crypt(PASSWD[user][0, 2]) == PASSWD[user]
      return Apache::OK
    else
      r.note_basic_auth_failure
      return Apache::AUTH_REQUIRED
    end
  end
end

class MyAuthz
  # MyAuthenを通過したものがrequire条件にマッチするかどうかを
  # 確認するためのクラス

  include Singleton
  def authorize(r)
    user = r.connection.user # r.get_basic_auth_pwは、すでに
                             # MyAuthen#authenticateで通われているので
                             # ここでは特にしなくてもよい
    r.requires.each do |x|   # requiresディレクティブの値にアクセス
      return Apache::OK if x[1] == 'valid-user'

      tmp, *users = x[1].split(/\s+/)
      return Apache::OK if tmp == 'user' && users.include?(user)
    end
    r.note_basic_auth_failure
    return Apache::AUTH_REQUIRED
  end
end

RubyAuthenHandlerRubyAuthzHandlerの違いは、 前者が与えられたユーザ名とパスワードの組み合わせが正しいことを 確認するためのものであるのに対し、 後者は認証されたユーザがrequireディレクティブの条件に 合致するかどうかを確認するためのものであるところにある。 したがって順番的には RubyAuthenHandlerRubyAuthzHandlerとなる。

この例ではユーザ名・パスワードの組み合わせを静的に記述してしまっているが、 このあたりでdRubyしてやったりすると、なお楽しめる。 ぜひチャレンジしてみてほしい。 なお、それぞれに必要となるメソッドは見てわかる通り authenticateauthorizeで、 いずれにもApacheサーバが受けたリクエストを表すオブジェクトが 引数として渡される。 このあたりの実態については、ふたたび、後述しよう。

mod_rubyとeRubyの密な関係

関係は密なのか蜜なのか。ま、それはさておき。

mod_rubyには標準でいくつかのライブラリが用意されている。 そのうちの一つにはerubyを使った eRuby処理クラスが含まれている。 サンプルではコメントアウトされている for Apache::ERubyRunの部分のコメントをはずしてみよう。 そうすると/eruby/以下のファイルが eRubyテキストであるとして扱われるようになるはずだ。

eRubyというのはePerlのようなもので、 テキスト中にRubyスクリプトを埋め込むための仕様である。 erubyはその実装の一つで、 Apache::ERubyRunオブジェクトを使うために必要となる。 わざわざ「実装の一つ」と言った通り、 eRubyにはもう一つの実装がある。それはERbだ。 erubyはC言語で実装されているのに対し ERbはpure rubyであるという点で違いがあるが、 動作上はどちらも大きな違いはない。 後者のERbを使いたければ for Apache::ERbRunの部分のコメントをはずすとよいだろう。 むろんその場合にはERbが別途必要となる*5

例として先のベンチマークに使ったスクリプトを eRubyテキストで書き直してみるとこのようになる。

<% require 'cgi' 
   c = CGI.new() %><!DOCTYPE HTML PUBLIC
 "-//W3C//DTD HTML 4.01 Transitional//EN"
 "http://www.w3.org/TR/html4/loose.dtd">
<HTML>
 <HEAD><TITLE>sample2</TITLE></HEAD>
 <BODY>
  <P>GATEWAY_INTERFACE: <%= c.gateway_interface || '(null)' %></P><BR>
  <UL><% c.params.each do |k, v| %>
        <LI><%= CGI::escapeHTML(k) %>: <%= CGI::escapeHTML(v.inspect) %></LI>
  <% end %></UL>
 </BODY>
</HTML>

なお、Apache::ERubyRunApache::ERbRunもNPH的には実行されず、 ステータスラインは自動的に補われる。 もしもステータスラインを自前で出力したければ eRubyスクリプト中でERuby.noheader = trueとしておくとよい。

eRubyと文字コード

ERuby.noheaderなんてものがでてきたところで 文字コード問題にも触れておこう。

文字コードはたとえば「日本語」をスクリプトで扱おうとするときに問題となる。 一口に問題と言ってもいろいろあるのだが、 ここで扱うのはブラウザで見た時に文字化けしてしまうという問題についてである。 最もありがちなのは、スクリプトが出力している実際の文字コードと HTTPのレスポンスヘッダ上にて示されている文字コードに齟齬があるということだ。 eRubyスクリプトから生成されるページで文字化けが起ったら、 ブラウザを操作してそのページについての情報を確認しよう。 文字コードがiso-8859-1などとなっていないだろうか。 にもかかわらずそのページで日本語を表示させようとしているのなら、 原因の少なくとも一つはそこにある。

mod_ruby上でerubyerbを使うとき、 レスポンスヘッダはerubyerbが自動的に生成してくれる。 ところが、erubyたちには スクリプトがどのような文字コードで出力しているのかを知るすべがなく、 しょうがないからデフォルトの文字コード情報を出力しているわけである。 よって、どのような文字コードで出力しているのかを明示してやれば 問題を解決できるであろうという話になる。

ERuby.charset = 'euc-jp'

ERuby.charsetに設定された文字列は レスポンスヘッダ上でそのまま使用される。 つまり、日本語EUCで出力しているのであれば この例のように'euc-jp'を設定してやればよいし、 MS漢字コードならば'shift_jis'を設定するわけだ。 JISコードなら'iso-2022-jp'である。 スクリプトにおいてこのようなコードが必要となるのは 日本語に限ったことではない。 英語以外の文字を使う場合にはほぼ確実に必要となるだろうから注意したい。

なお、erubyがデフォルトで出力する文字コード情報は erubyのコンパイル時に指定できることと、 ERuby.charsetによる設定が eruby全般に通用するテクニックであることを追記しておく。

mod_rubyの真の姿

これまでのところは楽しんでいただけただろうか。 いや、きっと「まだまだ」だろう。 そう、本当のmod_rubyの世界はこの後に開かれる。

今までのところは、たとえばCGIスクリプトによって 実現可能なものが多かった。eRubyを使った例にしたって erubyコマンドとmod_actionsの組み合わせで実現できないことはない。 Rubyスクリプトによる認証にだけは別だが、今のところそれだけだ。

たとえば。 CGIスクリプトにしろなんにしろ、 Apacheサーバが扱う多くのものは 実際のファイルに結びついていると言えよう。 しかし、mod_rubyによれば、ファイルとしての実体がない 情報を扱うことができるようになる。 まあ、そのこと自体がうれしい場面というのはそう多いわけではなかろうが、 一つの特徴ではあろう。

mod_rubyの裏舞台

mod_rubyの本質はApacheサーバ用のモジュールである mod_ruby.soにあるのは間違いがない。 しかし、mod_rubyによって動く「何か」の本体は mod_ruby.soにではなく、 ロードされるRubyスクリプトにこそある。

標準的にはConfig::CONFIG["rubylibdir"]apache以下に、いくつかのスクリプトがインストールされる。 以下のようなものたちだ。

apache/ruby-run.rb
apache/erb-run.rb
apache/eruby-run.rb

ここまではすでに登場している。 それぞれCGIスクリプト的な動作、 ERbを介した動作、erubyを使ったときのような動作を サポートするためのものだった*6

apache/rd2html.rb

RDテキストをHTMLに動的に変換するためのライブラリである。 内部でrdtoolの中核をなすライブラリを呼び出している。

ここまでの間に認証用のライブラリを作ったことからもわかる通り、 mod_rubyの仕様にあったライブラリ(というかクラス)を作ってやりさえすれば 自分の好きなような動作をmod_rubyによって実現できるようになる。 本質的にCGIのわく組みにとらわれなくなるため、 HTTPを汎用のプロトコルとして、Apacheサーバを 汎用のサーバとして活用する―なんてことだって やろうと思えば可能だろう。

apache/*.rbを作ろう

apache/*.rbに要求される仕様は比較的単純である。

まずクラスを一つ用意する。 そのクラスにはあなたがやりたい、そのことを作り込んでやる必要がある。 実際の作業をしてくれるのは、前述した通り、 そのクラスのインスタンスであり 具体的にはRubyHandlerに与えられた コードによって得られるオブジェクトである。 よって、たとえばFooBarなるクラスを作ったのであれば、 RubyHandlerではFooBarのオブジェクトを作れるような コードを指定してやることになる。

ここで、ポイント。

標準のapache/*.rbを見てもらえばわかると思うが、 それらによって提供されるクラスはすべてSingletonをincludeしている。 これには理由があって、Singletonにしていないと リクエスト毎にオブジェクトが生成されてしまい不経済なのである。 Singletonにするのは、mod_rubyから使うクラスに必須の要件ではないが、 特に必要がない限りSingletonにしておく方が良いだろう。

要るものはなんだ

さて、われらがFooBarクラスに必要なインタフェースはなんだろうか。 実はこの点においてはapache/*.rbとひとくくりにすることができない。 標準でインストールされているapache/*.rbは どれもmod_rubyから呼び出され、 mod_ruby環境下で動作するハンドラ(Rubyハンドラとでも呼ぼう)を 定義するものであが、そのハンドラにはいくつかの種類があるのだ。 代表的なのはRubyHandlerだが、 その他にRubyAuthenHandlerRubyAuthzHandlerが登場しているのを覚えているだろう。

インタフェースはそれがどのようなRubyハンドラかによって異なっている。 まずRubyHandlerについてだが、 このハンドラではhandlerというメソッドが リクエスト毎に呼び出される。 そして、すべての処理はその中で行われなくてはならない。 RubyAuthenHandlerRubyAuthzHandlerは それぞれauthenticateauthorizeというメソッドが呼ばれ、 やはりそれぞれの中においてすべての処理がなされるべきである*7

各メソッドはすべて一つの引数をともなって呼び出される。 その引数はApacheサーバが受け取ったリクエストそのものを表すもので、 Apache::Requestクラスのインスタンスである*8。 Apache::Requestがどのようなインタフェースを持っているかについては ドキュメントを 参照してもらうとして、 ここではapache/ruby-run.rbの内容を見てみることにしよう。

ruby-run.rb概観

序盤はこんな感じだ。

# ruby-run.rb (1/6)
require "singleton"

module Apache
  class RubyRun
    include Singleton

とりたてて問題ないだろう。 Apache::RubyRunクラスの定義のはじまりである。 先程述べたようにSingletonをincludeしているのが 特徴的と言えるかもしれない。

続く部分がApache::RubyRun、 すなわちCGIスクリプト互換環境のキモであり、 このクラスでは唯一定義されているメソッドである。

# ruby-run.rb (2/6)
    def handler(r)

前述したように、このrによって Apache::Requestオブジェクトを参照することができる。

# ruby-run.rb (3/6)
      if r.finfo.mode == 0
        return NOT_FOUND
      end

handlerの中ではじめに行っているのは リクエストに対応するファイルが存在するかどうかの確認だ。 r.finfo―すなわちApache::Request#finfoは リクエストされたURLに対応するパスについて File::statした結果得られるFile::Statオブジェクトを返すもので、 これを使って存在確認をしようとしているわけだ。 もしも存在していなければApache::NOT_FOUNDを返す。

Rubyハンドラオブジェクトのhandlerメソッドが返す値は、 そのままHTTPのステータスに反映される。 このケースのようにApache::NOT_FOUNDが返れば、 それは404(Not Found)というステータスに直結する。

ファイルの存在を確認できたら、 CGIスクリプトの実行が許されていることをチェックする。

# ruby-run.rb (4/6)
      if r.allow_options & OPT_EXECCGI == 0
        r.log_reason("Options ExecCGI is off in this directory", r.filename)
        return FORBIDDEN
      end

allow_optionsでリクエストされた情報について有効となる Optionsディレクティブによる設定値を取り出して、 その中にExecCGIが含まれているのを確認している。 ExecCGIが含まれていなければ Apache::FORBIDDEN、つまりステータスを403(Forbidden)とする。

最後にファイルが実行可能であるかの確認だ。

# ruby-run.rb (5/6)
      unless r.finfo.executable?
        r.log_reason("file permissions deny server execution", r.filename)
        return FORBIDDEN
      end

ファイルの実行ビットが立っていなければ、やはりエラーとする。

以上のチェックが終ったところでようやく スクリプトの実行に取りかかる。

# ruby-run.rb (6/6)
      r.setup_cgi_env
      Apache.chdir_file(r.filename)
      load(r.filename, true)
      return OK
    end
  end
end

CGIスクリプトとしての実行環境と互換を保つための準備を行い、 スクリプトそのものをloadする。 そこまで終ったところでApache::OKを返して、 このメソッドでの処理が終了する。

いかがだろうか。 前に例として出したMyAuthenMyAuthzについても 同じように読んで行けばよい。 何をやっているかは簡単に理解できるだろう。

これで可能性はぐっと広がった。…はずだ。

こんなもの作ってみました

おもしろいもの、おもしろいもの、おもしろいもの。 と考えてみたのだが、どうにもパッとするのを思いつかないので お茶な濁しぎみだがこんなものを考えてみた。かなりの手抜きであるが…。

require 'singleton'
require 'kakasi'

class Kakasi
  include Singleton

  def handler(r)
    if r.finfo.mode == 0
      return NOT_FOUND
    end
    t = Kakasi::kakasi('-s -Ha -Ka -Ja -Ea -ka', 
                       open(r.filename, 'r').read)
    r.headers_out['Content-length'] = t.size.to_s
    r.send_http_header

    r.write t

    return Apache::OK
  end
end

こうして使う。

RubyRequire apache/kakasi
<Location />
  SetHandler ruby-object
  RubyHandler Kakasi.instance
</Location>

これによって何が起こるのかというと、 まあ、みなさんがした想像の通りであろう。

まとめ?

どうも、ここにきてシマリが悪くてもうしわけない。 もっとおもしろくできないかと考えてはみたのだが、 すでに表の締切りがすぎていることもあり、 真の締切りがせまりつつもあるこの時点では これ以上の足掻きは難しいように思う。

そんなわけで、今回はココまで。 ではmod_rubyのMLで会おう。


*1「オブジェクト指向スクリプト言語Ruby」の 47ページを参照のこと。
*2実際には バージョン間の依存関係はこの通りではないが、 安定性などのことを含めるとこのように考えた方がよい。
*3詳しくはruby-run.rb概観を参照のこと。
*4ただし、このインタフェースは まだ実験的なものであり、将来、変更される可能性があることに注意されたい。
*5ちなみにDebianでもVineでも apt-get install erbでコトは済む。
*6現バージョン(0.8.5)では 内部で補助的なライブラリとしてapache/cgi-support.rbrequireしている。ただし同補助ライブラリは より新しいバージョンのmod_rubyでは廃止される予定である。
*7これらの仕様は mod_rubyバージョン0.8.5でのものである。今後変更される可能性もあるので 注意してほしい。
*8ちなみに mod_rubyから実行されるスクリプトにおいては Apache.requestによって同オブジェクトを参照できる。 間違ってApache::Requestとしてしまうと クラスそのものを指すことになるので注意したい。