極めよRuby道

第6回 CGI, mod_ruby そして eRuby

後藤謙太郎 <URL:http://www.notwork.org/~gotoken/mag/cmagazine/>

註: この文書は『C MAGAZINE』2001年1月号 に掲載された記事の元となるものに手を加えたものです.

記事中のプログラムを一つずつファイルにしたものもあります(→list)

中田さんのおかげでNetscapeでも読めるようになりましたが,まだちょっとカッコ悪いです。「CSSの書き方がなっとらん、こう書け」というお便りは歓迎します。

間違いを見つけたら,gotoken@notwork.org宛に御連絡くださると喜びます。

Copyright(c) 2000 by GOTO Kentaro. All rights reserved.


技術普及のご利益と… CGI (原理, cgi.rb の機能, フォームの解析, HTMLの生成支援, コマンドラインデバッグ, Cookie, mod_ruby 対応) mod_ruby (インストール, 使い方, mod_ruby の落し穴) eRuby もっとWeb 参考資料


技術普及のご利益と…

たぶんこの連載を読んでらっしゃる方の多くは俗にホームページと呼ばれる Web 資源を公開されてるんだと思います。そこで今回はWebの周辺についてお話しましょう。もしあなたがWebサイトを持ってなくてもきっと役に立つeRuby というドキュメントの動的生成言語も取り上げます。

HTTPに代表されるWeb技術の普及は、いろいろな意味で革命的な出来事でした。筆者がしばしばありがたいと思うのはWebブラウザの普及です。Webブラウザはネットワーク上の資源を閲覧することが大きな用途なんですけど、実際にはそれだけでなくてローカルなディスク上にあるテキストファイルを読むのにも重宝します。ご存知のように日本の文字コードは広く出回っているものだけでも数種類もある上に、行末の形態もUNIX、MS Windows、MacOSの3者でバラバラです。しかしこれらの違いも、Webブラウザさえあればその点を気にすることなく読むことが出来るのでずいぶん楽になりました。もっとも、文字を表示できても意味が分からなくて困ることもあったりしますが。

WebブラウザはHTTPクライアントでもあるので、とりあえずHTTPを話すものさえ作ればユーザインターフェイスの実装もずいぶん楽です。GUI を書く必要もないし、ユーザは自分の好みのクライアントを使うことが出来ますね。CSSを使って自分ごのみのスタイルで閲覧することも可能になって来ました。もちろん世界中の資源を容易に参照できるのもHTTPの大きな魅力ではあるんですけど、実務上レベルでは往々にして小規模な共同作業や資源共有をどうするかというレベルで苦労しているわけです。

一方で、特定のクライアントの普及に伴いアプリケーションの肥大化が目につきます。ツールボックスアプローチ*1 を好むUNIXの国の人だけでなく、多くの人にお勉強を強いています。ちょっとしたことでも巨大なマニュアルを調べなくてはならない羽目になった人は決して少なくないでしょう。

そんな現状では、基本概念を理解してちゃらっとスクリプトを書いてしまえる能力を身につけておくと、とても便利です。そんなわけで、段階を追って周辺技術を見て行きます。

CGI

CGI (common gateway interface) というのはサーバ側でプログラムを実行して動的にコンテンツを生成する仕組みの一つです。あなたのWebサイトでCGI の実行が許可されているかどうかはサービス内容を確認するか管理者に問い合わせるかして下さい。この節では、あなたがCGIを自由に実行できる幸せな人だと仮定して進めますね。

原理

CGIの用語ではCGIは枠組の名前で、実行されるプログラムはスクリプトと呼ばれます[Coa99]

ほとんどのサーバではCGIは環境変数と標準入力をつかってサーバからスクリプトに情報が渡されます。List1とList2はそのことを確かめる実験です。 ENVSTDINの内容をそれぞれ書き出しています。HTMLとしては読みにくいのですが、実はここでは読みにくいこと自体が伏線なんです ;-)

スクリプトの説明をすると、まず最初のプリント文はHTTPの約束でブラウザにこれから送るものの種類を教えてあげるためのものです。#1は、組み込み定数 ENVがハッシュのようにふるまうため、each のイテレータ変数 keydata にはキーと値がそれぞれ渡ります。ちなみに Hash#each は Hash#each_pair と同じものです。

一方、#2は行番号を表示するために、each_with_index を使っています。

-- List1 printenv.cgi: CGIの動作確認 
  #! /usr/local/bin/ruby

  def escapeHTML(string)
    string.
      gsub(/&/n, '&amp;').
      gsub(/\"/n, '&quot;').
      gsub(/>/n, '&gt;').
      gsub(/</n, '&lt;')
  end

  print "content-type: text/html"
  print "\r\n\r\n"
  print "<html><title>"
  print "printenv - result"
  print "</title><body>\n"

  # greeting message
  case Time.now.hour
  when 5..9
    puts "<p>Good morning!</p>"
  when 10..18
    puts "<p>Helo!</p>"
  else
    puts "<p>Good night!</p>"
  end

  # report ENV
  print "<h1>print env</h1>"
  print "<h2>Environment</h2>"
  print "<dl>"

  ENV.each do |key, data|            #1
    puts "<dt>#{key}</dt>"
    puts "<dd><code>"
    puts "#{escapeHTML data.dump}"
    puts "</code></dd>"
  end

  print "</dl>"

  # report STDIN
  print "<h2>Standard input</h2>"
  print "<pre>"
  STDIN.each_with_index do |line, i| #2
    puts "#{i}: #{line}"
  end
  print "</pre>"

  print "</body></html>"

一方、ユーザインターフェイスであるHTML(List2)では、FORM要素を使ってCGI を呼び出します。さて、実行してみると、FROMのMETHOD属性がREQUEST_METHOD という環境変数で渡っていることが分かります。また、GETのときは内容が QUERY_STRINGで渡されるのに対し、POSTのときは標準入力から渡されていますね。

-- List2 printenv.html: printenv.cgi用HTML
  <html>
    <title>printenv</title>
    <body>
    <h1>Print ENV</h1>
    <h2>GET</h2>
      <form action="printenv.cgi" method="get">
        <textarea name="get" rows=2 cols=10></textarea>
        <input type="checkbox" name="button" checked>
        <input type="submit" value="submit"><br>
      </form>
    <h2>POST</h2>
      <form action="printenv.cgi" method="post">
        <textarea name="post" rows=2 cols=10></textarea>
        <input type="checkbox" name="button" checked>
        <input type="submit" value="submit"><br>
      </form>
    </body>
  </html>

また、いずれの場合もINPUTやTEXTAREA要素のNAME属性とVALUE属性が、「&」で連結されて「get=hoge&buton=on」のように渡されています。これを解析すればフォームから渡されたものが何だったかが分かるわけです。

しかしこの辺の処理は決まりきっているので、CGIを書くたびに同じ処理を書くのはあまりに非人間的です。そこでRubyではこれらの処理を行う cgi.rb というライブラリが標準で添付されています。

cgi.rb の機能

cgi.rb は単にフォームの解析処理を行うだけでなく、

といった機能を提供しています。

フォームの解析

まず、cgi.rb を使うには、CGIプログラムの冒頭で

require "cgi"
cgi = CGI.new

とします。この cgi というオブジェクトはハッシュのように扱うことが出来ます。すなわち、NAME属性の値をキーとして対応する値を取り出すことが出来ます。たとえば、button というNAME属性に対応するVALUE属性の値は、

cgi["button"]

で得ることが出来るわけです。また、環境変数のうち、REMOTE_HOST など CGI/1.1[Coa99]で提案されているメタデータに関しては CGI#remote_host のようにメソッドによって参照することが出来ます。

HTMLの生成支援

CGI.new に引数を与えることで、生成するHTMLのレベルを指定できます。たとえば次のようにすれば、HTML 4.0 Transitional なHTMLを生成します。

cgi = CGI.new("html4Tr")

CGI.newの引数に指定するキーワードはcgi.rbを参照して下さい。なお、 cgi.rbはこの連載ではおなじみのRDで書かれています。

一方、HTMLを出力することを一風代わった方法で支援しています。まず、CGI の出力メソッドとして、CGI#outが用意されています。このCGI#outにはブロックを渡し、その結果の文字列を出力するのです。更に、HTMLの要素に対応するメソッドが用意されており、これを使ってHTMLの生成を支援しています。リスト3はこれらの機能を使った簡易なうらない(?)です。fortuneを使って気のきいたセリフを表示します。また、このスクリプトにはList1にあった「"content-type: text/html"」のようなHTTPヘッダを出力するするための処理が書かれていませんが、そのようなことは CGI#out がやってくれるのです。CGI#out

cgi.out("type" => "image/png")

のように引数を与えれば、HTTPヘッダを変えることもできるので、もちろん画像を出力することもできます。文字コードを知らせるには次のようにします。

cgi.out("charset" => "euc-jp")

CGI#outの引数はハッシュなので、順不同で複数のキーと値のペアを渡すことができます。

cgi.out("charset" => "euc-jp",
        "type => "text/plain")

やや注意が必要な点は、ブロックの評価結果は文字列でなければならないので、要素を並べる場合は「+」演算子でつなげてやらなければならないことです。

-- List3 fortune.cgi
   #!/usr/local/bin/ruby
   require "cgi"

   FORTUNE = "/usr/games/fortune"

   cgi = CGI.new("html4Tr")
   cgi.out {
     cgi.html {
       cgi.head {
         cgi.title{"fortune"}
       } +
         cgi.body {
         cgi.h1{"fortune"} +
         cgi.pre {
           msg = `#{FORTUNE}`
           msg.gsub!(/^/, "   ")
           CGI::escapeHTML(msg)
         }
       }
     }
   }

コマンドラインデバッグ

オフラインモードと銘打ったデバッグ機能はとても便利です。CGIスクリプトをコマンドラインから起動し、フォームデータを入力すれば name=value の形式で入力すればCGIの応答をサーバを介さずに見ることができます。UNIXなら teeコマンド(1)で応答をファイルに保存しておいて、ブラウザで確認することもできますね。

Cookie

Cookie(クッキー)というのは、数回に渡るHTTPのセッションをひとつながりのものとして認識するなどの用途で使われる技術です。端的な例としてはWebでのオンラインショッピングで「買いものカゴ」に相当するものを管理するのに使われます。cookieの語源には諸説ありますが、jargon file に載っている「クリーニング屋で洗濯物の引き替えに使うトークン」というのが真相のようです。日本では洗濯物を出した時に引き替え用にお店から伝票を渡され、きれいになった洗濯物を受けとる時に今度はその伝票を見せてその洗濯物を受けとる資格があることを証明しますね。このたとえの通りで、つまりブラウザから GET 要求を出したら cookie というものを渡され*2、それを次回以降の要求の際に一緒に渡すことでブラウザの一意性が判定されるのです。

-- Fig1 cookie利用のあらまし
      ##########################################
      #### 矢印は少し斜め下向きにして下さい ####
      ##########################################

             ブラウザ                       サーバ
                             GET要求1
                       ------------------->
                                            cookie作成
                           cookie要求
                      <-------------------
     (cookieを
      受けとるか判断)    GET要求1への応答
                      <-------------------
       リンクを手繰る
                         cookie付GET要求2
                       ------------------->
                                            cookieを使った処理
                         GET要求2への応答
                      <-------------------

cgi.rb を使ってCookieオブジェクトを生成するには CGI::cookie::new を使います。CGIからCookieオブジェクトを送るには、CGI#out の引数の中に、

cgi.out("cookies" => 
         [cookie1, cookie2])

のように書きます。

一方スクリプトに送られてきた要求からcookieを取り出すには CGI#cookies を用います。その値はハッシュで、cookieの名前をキーとします。詳細は再び cgi.rb を参照して下さい。

mod_ruby 対応

以下で述べるmod_rubyを使っているときとそうでないときに同じCGIスクリプトを用いることができます。これは、HTTPヘッダの生成などで有用です。

mod_ruby

mod_ruby は前田修吾さんによって開発された Apache [ASF] のためのモジュールです*3。mod_ruby は、先輩にあたる mod_perl と同様に、Ruby インタプリタをhttpdに組み込むことで、CGI実行時のオーバーヘッドを削減しCGI 起動の高速化を実現するためのものです。たとえば、リダイレクション*4のためだけに書かれた CGIなどはとりわけ高速に実行される必要があるし、相対的にインタプリタの起動の方が実行時間を食う場合が多いので特に向いているといえます(List4)。

また、単にインタプリタを実現するだけでなく、後で述べる eRuby という RubyベースのPHPに類似の機能もオプションとして提供することができます。このeruby+mod_rubyという組み合わせは、めちゃめちゃ便利なので、これからのApache運用の定番になると予想しています。

-- List4 言語別インデックスをリダイレクトするためのmod_rubyコード

  # index.mrb

  # コンフィグレーション
  LOCATION_MAP = {
    "en" => "en/index.html",
    "ja" => "ja/index.html"
  }
  DEFAULT_LOCATION = "en/index.html"
  DEFAULT_DOMAIN = "www.domain.tld"

  # URIの正規化とHostの取得
  req = Apache.request
  host = req["Host"] || DEFAULT_DOMAIN
  dir = File.dirname(req.uri)
  if dir[-1] != ?/
    dir.concat(?/)
  end

  # 言語の判別とURI生成
  langs, = 
    (req["Accept-Language"] || "").split(/;/)
  location = DEFAULT_LOCATION
  for lang in langs.to_s.split(/,/)
    lang.strip!
    if LOCATION_MAP.key?(lang)
      location = LOCATION_MAP[lang]
      break
    end
  end

  # 応答
  req["Location"] = 
    "http://" + host + dir + location
  exit Apache::HTTP_MOVED_PERMANENTLY

インストール

ポート80番を使った httpd のインストールには root 権限が必要なので、サーバの管理者の方には、この機会に是非 mod_ruby を導入することをお薦めします。インストールの際には一緒に、erubyもインストールするとおいしさ倍増です。手順はおおむね次のようになります。

  1. インストールするには、まず、DSO(Dynamic Shared Object)対応のApache をあらかじめインストールしておきます。すでにDSOを組み込んであれば再インストールの必要はありません。DSOは Apache 1.3.x の機能です。例えば、Apache/1.3.14 の場合、Apache 自体の make 時になにか一つでも --enable-shared しておけば、DSOの機能が使えるようになります。configure のオプションの詳細については Apache の README.configure を御確認下さい。
  2. インストールが済んだら、Apacheを起動してうまくインストールできたか確かめておきましょう。Apacheの再起動には apachectl start を使わなくてはなりません。
  3. 次に、erubyをインストールします。erubyは[RAA]に登録されています。
  4. そして、mod_ruby をやはり[RAA]から持ってきて、インストールします。
  5. 最後に、Fig2の3行を Apache の httpd.conf 適当な場所に追加し、 Apache を再起動します。Apacheの再起動にも apachectl restart を使わなくてはなりません。
-- Fig2 httpd.conf に追加するもの

    LoadModule ruby_module libexec/mod_ruby.so
    AddType application/x-httpd-eruby .rhtml
    AddHandler ruby-script .mrb

使い方

上のように .mrb を ruby-script のハンドラとして登録しておけば、名前が「.mrb」で終るファイルをHTTPでアクセスすると、httpdに組み込まれたインタプリタを利用してそのまま実行されます。chmodで実行可能にしておく必要はありません。Rubyで書かれたCGIはそのまま mod_ruby で実行することができます。

同様に、名前が「.rhtml」で終るファイルをアクセスすると以下で述べる eRuby として解釈されます。論より証拠、説明は抜きにしてList1をeRubyで書き直したものをList5に示します。eRubyではHTMLが主役となっているので、 printなどが少ないためRubyでCGIを書いたときよりもはるかに読みやすいですね。

-- List5 printenv.cgi: List1をerubyで書き直したもの
  <html><title>printenv - result</title><body>
  <% require "cgi" %>
  <h1>greetings</h1>
  <p>
  <%
     case Time.now.hour 
     when 5..9 
       pirnt "Good morning!"
     when 10..18
       print "Helo!"
     else 
       print "Good night!"
     end 
  %>
  </p>
  <h1>print env</h1>
  <h2>Environment</h2>
  <dl>
  <% ENV.each do |key, data| %>
     <dt><%= key %></dt>
     <dd>
       <code>
         <%= CGI::escapeHTML data.dump %>
       </code>
     </dd>
  <% end %>
  </dl>

  <h2>Standard input</h2>
  <pre>
  <% 
    STDIN.each_with_index do |line, i|
      print "#{i}: #{CGI::escapeHTML line.dump}\n"
    end
  %>
  </pre>
  </body></html>

mod_ruby の落し穴

mod_ruby は便利で高速ですが、注意しなければならない点が一つあります。それは同じexec()されたhttpdが同じRubyインタプリタを何度も使う場合があるので、スクリプト起動時にインタプリタが初期化されないという点です。たとえば、Ruby で外部ファイルを読み込む関数である require は、同じファイルを決して2回読みません。このため、require されるファイルを書き直しても、そのhttpdが死ぬまでは再度読み込まれないので、書き直したことが動作に反映されません。

言いかえれば、mod_rubyによる高速化はやはりチューンのためにあるので、最初から使うのにはちょっと無理があるということでしょう。そこで、はじめは CGIとして使い、動作が安定したことを確認してから、おもむろに「拡張子」を「.mrb」に書き換えるのが良いでしょう[ruby-list:25478]

eRuby

さて、ここまではWebサイト作成に依存したお話でしたが、eRubyの適用範囲は CGIサイトに限りません。eRubyというのは言語の名前で「テキストファイルに埋め込まれたRuby」、つまり embedded Ruby から付けられた名前です。そして、erubyというのはその処理系のひとつなわけです。他には、咳さんによる ERb というRubyでの実装もRAAにあります。

さてerubyをインストールすると、erubyというフィルターコマンド*5もRubyインタプリタと同じ場所にインストールされます。

erubyは <% %> で囲まれた部分が特別な意味を持ちます。基本的には、 <% %> にRubyのコード片を書くことができます。eRubyの文法をFig3に掲げます。

-- Fig3 eRuby の文法

  ========================================================
  書き方           意味
  --------------------------------------------------------
  <% ... %>        ... の出力でそこを置き換える
  <%= ... %>       ... を評価した結果でそこを置き換える
  <%# ... %>       コメント
  % ではじまる行   その行が <% ... %> となっているのと同じ
  ========================================================

eruby に -d オプションを指定すれば、どういう風に解釈されるかを見ることができます(Fig4)。

-- Fig4 eruby -d の結果($はシェルプロンプト)

  $ cat foo.rhtml
  % for i in 1..3
  <H1>section<%= i %></H1>
  % end
  $ ./eruby -d foo.rhtml
  --- generated code ---
      1:  for i in 1..3
      2: print "<H1>section"; print(( i )); print "</H1>\n"
      3:  end
  ----------------------
  <H1>section1</H1>
  <H1>section2</H1>
  <H1>section3</H1>
  $

これをみて分かるように、eRubyの適用範囲は何もHTMLに限りません。とくに、 SGMLやXMLアプリケーションとは相性が良いでしょう。また、オンデマンドで用いなくても、静的に文書を生成するのにも有効です。たとえば、望みの位置に

Last modified: <%= Time.now %>

と書いておいてerubyを通せば、そこに時刻が書き込まれます。LaTeXのなかでちょっと凝った計算をしたい時にRubyを使うなんてこともできますね*6。 また定型文書のテンプレートなどにも使えます。Ruby には Perl のFormatに相当するものがありませんが、簡単なものなら eRuby で補えるわけです。もし面白い使い方を見つけたら、メーリングリストで紹介してみてはいかがでしょうか。みんなでわいわいやるのも楽しいと思いますよ。

もっとWeb

Webは人気のサービスなので、Rubyでも数多くのWeb関連ソフトウェアが開発されています。是非RAAをのぞいてみて下さい。最近の目だったアプリケーションには咳さんとなひさんのRWikiがあります[RWiki]。これはRDで書き込むWikiクローンです。Wikiというのは、みんなで書き込めるWebサイトとでもいうべきもので、とても面白い本当に新しいメディアだと思います。

またCGIなどを作るのにはURIやHTMLを扱う技術も必要ですが、そういうものも RAAには登録されています。いくつかのHTML 関連ライブラリについては筆者らが紹介した記事[IPR2]もあるので興味があればご参照下さい。また、コンテンツ作成に直接関係しなくても資源をISPにアップロードするようなWebサイト作成支援ツールも多くあります(例えば[webup])。

今回はコンテンツとも呼ばれる閲覧される方の資源を紹介してきました。一方でクライアントや、あるいはサーバを書くのも容易です。とくにサーバをこれほど楽に書ける言語はそうはないでしょう。そこで次回は標準ライブラリの Net::HTTPを使ったクライアントや、WEBrick[WEBrick]などをつかったサーバの作成をみてみたいと思います。

参考資料

[ASF]

The Apache Software Foundation, ``The Apache HTTP server project'', <URL:http://httpd.apache.org>

[Coa99]

Ken A L Coar, ``The WWW Common Gateway Interface Version 1.1'', Internet-Draft (obsolete), <URL:http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html> (25 Jun 1999)

[ERb]

Masatoshi SEKI, ``ERb'', [RAA]参照

[IPR2]

後藤謙太郎, 後藤裕蔵, 高橋征義, 渡辺哲也, 『Rubyではじめるインターネットプログラミング第2回 クライアント(前編) -- ステートレスなプロトコルの代表 HTTP』 オープンデザイン 39, pp.36-75, CQ出版 (2000)

[RAA]

``Ruby Application Archive'', <URL:http://www.ruby-lang.org/en/raa.html>

[ruby-list:25478]

Yukihiro Matsumoto, <URL:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/25478>

[RWiki]

Masatoshi SEKI and NaHi, ``RWiki'', <URL:http://www.jin.gr.jp/~nahi/RWiki/>

[webup]

Internet Programming with Ruby Project, ``webup.rb'', <URL:http://www.notwork.org/ipr/4th/> (オープンデザイン 41, 82-97, CQ出版 (2000) も参照)

[WEBrick]

Internet Programming with Ruby Project, ``WEBrick'', [RAA]参照


*1 小さな道具を組み合わせて使う信条
*2 cookie の情報はHTTPヘッダに含まれます
*3 後日談: mod_ruby.netがオープンしました
*4 指定されたURLとは別のURLにクライアントを導くこと
*5 標準入力から読んだテキストを加工して標準入力に出すもの。erubyのようにファイル名を指定することもできるようになっているものが多い
*6 LaTeX のコメントは % で始まるのでちょっと注意が必要