著者: \ay, 技術監修: 前田修吾
前号の記事が好評をいただいたに違いないという推測のもとに、 今回も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 | |||||
|---|---|---|---|---|---|---|
| 並列度 | 処理時間 [秒] |
リクエスト/秒 | 転送レート [バイト/秒] |
処理時間 [秒] |
リクエスト/秒 | 転送レート [バイト/秒] |
| 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 |
あくまで一例ではあるが、大きな差が認められることがわかるだろう。
これらの差はApacheサーバ自身にRubyインタプリタを内蔵してしまうことによって、 Rubyインタプリタ自体を複数の処理で使いまわすことに由来している。 したがって、この「優勢」には副作用がある。 一般に副作用はRubyインタプリタ全体の状態に関して生ずるが、 それはたとえばこんなものだ。
requireが働くとは限らない。requireされるファイルの中で
動的な初期化をしているとハマる原因となる。
まあ、それがライブラリであった場合には、
そんなことをするライブラリが悪いと言えよう。
ライブラリでない場合(ある種のデータとか)には
requireの代りにloadを使うとよいだろう。
$$が複数の処理で同じになる可能性がある。一時ファイルやロックにおいて、 この値がユニークであることを期待している場合には 正常に動作しなくなってしまうだろう。
$SAFEmod_rubyではデフォルトで
$SAFEの値を1にしている。
このため、一部のメソッド(File#openなど)に
外部から入力された値をそのまま渡すと
SecurityErrorが起こることがある。
共有されると言っても、それはApacheサーバの各プロセス内での話である。 ご存知のようにApacheサーバは複数プロセスから構成され、 各プロセスはあるタイミングでその一生を終えるのだがから、 この共有を積極的に利用することにはほとんど望みがない。 結局、グローバル変数を使ってはならない というオキテ*1はここでも生きてくるわけだ。
一方で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、eruby、rubyのバージョンが
それぞれ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スクリプト的というのは、
スクリプト自体の実行環境全般についてを指している。
よって、スクリプト自体に実行属性がなければならないことと、
OptionsにExecCGIが入っていなければならないことに
注意してほしい*3。
ちなみにサンプルではコメントアウトされている
二つの部分―for Apache::ERubyRunおよび
for Apache::ERbRunとある部分は
それぞれerubyまたはERbを使って
eRubyテキストを扱うための設定例となる。
mod_rubyとeRubyの関係についても後述するので
適宜参照されたい。
それでははじめよう。
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.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スクリプトには開放されていない。
よって、一般に行われる.htaccessとhtpasswdによるのではなく、
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>
ここでAuthType、AuthName、requireについては
.htaccessとhtpasswdでの認証を行う場合のそれと同義である
と考えてもらってさしつかえない。
RubyAuthenHandlerとRubyAuthzHandlerは―ピンときただろうか。
そう、実際の認証を司るオブジェクトを指定するためのディレクティブで、
mod_rubyによってもたらされたものだ。
ここで指定しているMyAuthenとMyAuthzは
apache/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
RubyAuthenHandlerとRubyAuthzHandlerの違いは、
前者が与えられたユーザ名とパスワードの組み合わせが正しいことを
確認するためのものであるのに対し、
後者は認証されたユーザがrequireディレクティブの条件に
合致するかどうかを確認するためのものであるところにある。
したがって順番的には
RubyAuthenHandler→RubyAuthzHandlerとなる。
この例ではユーザ名・パスワードの組み合わせを静的に記述してしまっているが、
このあたりでdRubyしてやったりすると、なお楽しめる。
ぜひチャレンジしてみてほしい。
なお、それぞれに必要となるメソッドは見てわかる通り
authenticateとauthorizeで、
いずれにもApacheサーバが受けたリクエストを表すオブジェクトが
引数として渡される。
このあたりの実態については、ふたたび、後述しよう。
関係は密なのか蜜なのか。ま、それはさておき。
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::ERubyRunもApache::ERbRunもNPH的には実行されず、
ステータスラインは自動的に補われる。
もしもステータスラインを自前で出力したければ
eRubyスクリプト中でERuby.noheader = trueとしておくとよい。
ERuby.noheaderなんてものがでてきたところで
文字コード問題にも触れておこう。
文字コードはたとえば「日本語」をスクリプトで扱おうとするときに問題となる。 一口に問題と言ってもいろいろあるのだが、 ここで扱うのはブラウザで見た時に文字化けしてしまうという問題についてである。 最もありがちなのは、スクリプトが出力している実際の文字コードと HTTPのレスポンスヘッダ上にて示されている文字コードに齟齬があるということだ。 eRubyスクリプトから生成されるページで文字化けが起ったら、 ブラウザを操作してそのページについての情報を確認しよう。 文字コードがiso-8859-1などとなっていないだろうか。 にもかかわらずそのページで日本語を表示させようとしているのなら、 原因の少なくとも一つはそこにある。
mod_ruby上でerubyやerbを使うとき、
レスポンスヘッダはerubyやerbが自動的に生成してくれる。
ところが、erubyたちには
スクリプトがどのような文字コードで出力しているのかを知るすべがなく、
しょうがないからデフォルトの文字コード情報を出力しているわけである。
よって、どのような文字コードで出力しているのかを明示してやれば
問題を解決できるであろうという話になる。
ERuby.charset = 'euc-jp'
ERuby.charsetに設定された文字列は
レスポンスヘッダ上でそのまま使用される。
つまり、日本語EUCで出力しているのであれば
この例のように'euc-jp'を設定してやればよいし、
MS漢字コードならば'shift_jis'を設定するわけだ。
JISコードなら'iso-2022-jp'である。
スクリプトにおいてこのようなコードが必要となるのは
日本語に限ったことではない。
英語以外の文字を使う場合にはほぼ確実に必要となるだろうから注意したい。
なお、erubyがデフォルトで出力する文字コード情報は
erubyのコンパイル時に指定できることと、
ERuby.charsetによる設定が
eruby全般に通用するテクニックであることを追記しておく。
これまでのところは楽しんでいただけただろうか。 いや、きっと「まだまだ」だろう。 そう、本当のmod_rubyの世界はこの後に開かれる。
今までのところは、たとえばCGIスクリプトによって
実現可能なものが多かった。eRubyを使った例にしたって
erubyコマンドとmod_actionsの組み合わせで実現できないことはない。
Rubyスクリプトによる認証にだけは別だが、今のところそれだけだ。
たとえば。 CGIスクリプトにしろなんにしろ、 Apacheサーバが扱う多くのものは 実際のファイルに結びついていると言えよう。 しかし、mod_rubyによれば、ファイルとしての実体がない 情報を扱うことができるようになる。 まあ、そのこと自体がうれしい場面というのはそう多いわけではなかろうが、 一つの特徴ではあろう。
mod_rubyの本質はApacheサーバ用のモジュールである
mod_ruby.soにあるのは間違いがない。
しかし、mod_rubyによって動く「何か」の本体は
mod_ruby.soにではなく、
ロードされるRubyスクリプトにこそある。
標準的にはConfig::CONFIG["rubylibdir"]の
apache以下に、いくつかのスクリプトがインストールされる。
以下のようなものたちだ。
apache/ruby-run.rbapache/erb-run.rbapache/eruby-run.rbここまではすでに登場している。
それぞれCGIスクリプト的な動作、
ERbを介した動作、erubyを使ったときのような動作を
サポートするためのものだった*6。
apache/rd2html.rbRDテキストを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だが、
その他にRubyAuthenHandlerや
RubyAuthzHandlerが登場しているのを覚えているだろう。
インタフェースはそれがどのようなRubyハンドラかによって異なっている。
まずRubyHandlerについてだが、
このハンドラではhandlerというメソッドが
リクエスト毎に呼び出される。
そして、すべての処理はその中で行われなくてはならない。
RubyAuthenHandlerとRubyAuthzHandlerは
それぞれauthenticateとauthorizeというメソッドが呼ばれ、
やはりそれぞれの中においてすべての処理がなされるべきである*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を返して、
このメソッドでの処理が終了する。
いかがだろうか。
前に例として出したMyAuthenやMyAuthzについても
同じように読んで行けばよい。
何をやっているかは簡単に理解できるだろう。
これで可能性はぐっと広がった。…はずだ。
おもしろいもの、おもしろいもの、おもしろいもの。 と考えてみたのだが、どうにもパッとするのを思いつかないので お茶な濁しぎみだがこんなものを考えてみた。かなりの手抜きであるが…。
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.rbを
requireしている。ただし同補助ライブラリは
より新しいバージョンのmod_rubyでは廃止される予定である。
*7これらの仕様は
mod_rubyバージョン0.8.5でのものである。今後変更される可能性もあるので
注意してほしい。
*8ちなみに
mod_rubyから実行されるスクリプトにおいては
Apache.requestによって同オブジェクトを参照できる。
間違ってApache::Requestとしてしまうと
クラスそのものを指すことになるので注意したい。