もくじチック
mod_dtclとは?
インストール
何事もとっつきが基本です
ブラウザの中の環境変数とCGI変数
CGIぽくやっちゃってみよう
自己完結型フォームスクリプト(ρテク)
HTML以外のコンテンツを生成する
カウンタを作ってみる
クッキーを使う


mod_dtclとは?

 mod_dtclは、 WebサーバーApacheの拡張モジュールで、 Tcl言語によるサーバーサイドスクリプト処理を可能にします。 サーバーサイドスクリプト処理とは、Webブラウザユーザーの入力を元に、 特殊なスクリプトを埋め込んだHTML文書「もどき」を Apache上で解釈/変換して、HTML文書などの「コンテンツ」 を動的に生成してWebブラウザユーザーのパソコンに送り返すことを指します。
 同じような処理を行う拡張モジュールとしてmod_dtclの大先輩にあたるのが、 PHP(PHP: Hypertext Preprocessor)と JSP(Apache/JServ/mod_jk)です。 PHPはPHP言語という専用のスクリプト言語を使ってサーバーサイドスクリプト処理を行うモジュールで、

  • Perlによく似た平易なスクリプト言語による記述
  • Cookieをラップした高水準のセッション管理が可能
  • 各種RDBMSへのアクセス機能の強力なサポート
などが特徴として挙げられます。またApache/JServは Java Server Pages(JSP) やその基盤技術であるサーブレット(Servlet)の解釈エンジンである Tomcat を呼び出す機能をApacheに追加する拡張モジュールで、 (これはmod_jkやTomcatというよりJSPの特徴になりますが)
  • Java言語によるしっかりしたオブジェクト指向記述
  • Java Development Kit(JDK)が持つ豊富なAPIが利用可能
  • XMLなどの周辺技術との密接な関連
などが特徴です。
モジュール名解釈実行系使用する言語
PHPPHPPHP言語
mod_jkTomcatJava言語
mod_dtclTclTcl言語

 mod_dtclはPHPやTomcat/mod_jkに比べて遥かにマイナーですが、 Tcl言語の持つ拡張性により、 C言語で書かれた処理をApacheに埋め込むような場合には非常に便利です。 例えば、PHPには組み込みで用意されている各種RDBMSへの接続機能ですが、 Tclの場合はOratcl(Oracleへの接続)やFbsql(MySQLへの接続)、 libpgtcl(PostgreSQLへの接続)などの拡張ライブラリを入手すれば、 PHPと同様データベースアクセスが可能になります。
 mod_dtclはApache Tcl Project の一環として開発されており、Apache本体や多くの拡張モジュールと同様、 フリーウェアとして入手できます。


mod_dtcl 0.10.1 のインストール(UNIX編)

 今回は、次の環境とバージョンでインストールを試みました。

  • Linux カーネル2.2.14(Kondara MNU/Linux 1.1)
  • gcc egcs-2.91.66
  • Apache 1.3.14
  • Tcl/Tk 8.4a1
  • mod_dtcl 0.10.1
mod_dtclをApacheに組み込むには、
  • Apacheのビルド時に本体に組み込む。(static)
  • 共有ライブラリ(DSO)にする。(shared)
の2通りがあります。ここではstaticの例で試します。 なぜかというと…なぜかというと… sharedビルドは失敗したからです(Apacheが異常終了するのよう)。 それはともかく、staticビルドの場合には、 Apache 1.3.** のソースアーカイブが必要になります。 Apacheのインストールについてはここでは省略します。
  1. http://tcl.apache.org/ からソースアーカイブをダウンロードしてきます。
  2. builddtbl.sh をテキストエディタで編集します。場所は次の3か所です。
    • TCLSH= (tclshの実行形式の名前)
    • APACHE= (Apacheのソースディレクトリ。staticの場合のみ必要)
    • INC= (Apacheのincludeディレクトリ)
    例えば次のようになります。
    # builddtcl.sh (抜粋)
    #
    # Name of tclsh - on FreeBSD, this is probably tclsh8.2
    TCLSH=tclsh8.4  ######### CHANGEME ##########
    # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    APACHE=$HOME/install/apache_1.3.14
    INC=/usr/local/apache/include
    
  3. まず、mod_dtclのライブラリを作成します。
    % sh builddtcl.sh static
    
    Linuxだとかなり警告が出ますが、「mod_dtcl.a」というファイルができていれば、 一応完成ということで。
  4. 今度はそのライブラリをApacheのソースに移します。
    % sh builddtcl.sh install
    
    これで mod_dtcl.a がApacheのソースディレクトリの中、この場合ですと 「$APACHE/src/modules/mod_dtcl/mod_dtcl.a」 としてコピーされます。
  5. Apacheをconfigure→makeでビルドします。configureのときに、 mod_dtclも使えという指令(南国秘密指令)をつけます。
    % cd ../apache_1.3.14
    % sh configure --activate-module=src/modules/mod_dtcl/mod_dtcl.a \
        --enable-shared=max
    % setenv EXTRA_LIBS "-ltcl8.4 -lm"
    % make
    % su root
    # make install
    
    この場合は、configureで --prefix=オプションをつけていないので、 Apacheは/usr/local/apache にインストールされます。そのときに、 staticビルドだとhttpdの中にTclが直接組み込まれているので、 次のようにして確認できます。
    % strings /usr/local/apache/bin/httpd | grep Tcl
    Tcl_GetChannelName
    Tcl_GetStringResult
    ...
    
    DSOとしてビルドした場合には、 他のApacheに同梱のモジュールライブラリの中にひっそりと混じって、 /usr/local/apache/libexec/mod_dtcl.so ができていればOKです。
  6. Apacheの設定ファイル httpd.conf を編集します。 次の各行を追加します。
    AddModule mod_dtcl.c
    AddType application/x-httpd-tcl .ttml
    AddType application/x-dtcl-tcl  .tcl
    
    また今回は失敗したので不要でしたが、DSOとしてビルドしたときのみ、 次の行も追加しておきます。
    LoadModule dtcl_module          libexec/mod_dtcl.so
    
  7. Apacheを再起動します。 このとき、起動ユーザーの環境変数 LD_LIBRARY_PATH に、 libtcl8.*.soのパスが含まれていないと起動できないので注意が必要です。
    % su root
    # /usr/local/apache/bin/apachectl restart
    


Windows用バイナリ配布のインストール

 mod_dtclの総本山には、 寄贈ソフトウェアとして、mod_dtclをWindows環境でコンパイルしたバイナリ(DLL) 配布があります。NT用と書かれていますが、下記の通りWindows98でも使えました。 環境は次の通りです。

OSApacheTcl/Tkmod_dtcl
MS-Windows98SE1.3.248.4a1不明(2001/11/05版)

作業手順は次の通りです。

  1. Tcl/TkのDLLに環境変数PATHが通っていることを確認します。
  2. 上記サイトからアーカイブをダウンロードして展開します。
  3. with_version/mod_dtcl.so を、$APACHE_HOME/Modules にコピーします。
  4. $APACHE_HOME/conf/httpd.conf を編集し、次の行を追加します。

    LoadModule dtcl_module  modules/mod_dtcl.so
    AddModule mod_dtcl.c
    AddType application/x-httpd-tcl .ttml
    AddType application/x-dtcl-tcl  .tcl
    

  5. Apacheを再起動します。 「Apache/1.3.24 (Win32) mod_dtcl running...」と、起動メッセージに 「mod_dtcl」の文字が現れたら成功です。


何事もとっつきが基本です(サンプル1)

では、ちょっとしたサンプルで mod_dtcl をつついてみましょう。

<HTML>
<HEAD><TITLE>mod_dtclの簡単なサンプル</TITLE></HEAD>
<BODY>
<?
hputs "<H1>Hello mod_dtcl world.</H1>"
hputs "現在の時刻: [clock format [clock seconds] -format {%Y/%m/%d %H:%M:%S}]"
hputs "<BR>"
hputs "OK.<BR>"
?>
</BODY>
</HTML>

これが mod_dtcl を埋め込んだHTML文書です。この例から分かることは、 ファイル名の拡張子は「.ttml」にしないといけないということです。
分かるか?
えーと、あれ??(しどろもどろ)
と、とにかく、そうなんです!正確には、httpd.conf で
AddType application/x-httpd-tcl .ttml
このような行で指定した拡張子にすればOKです。 ここでは sample1.ttml と名付け、通常のHTML文書と同様、 /usr/local/apache/htdocs 以下のディレクトリに置きます。
 さて、仕切り直し。上の例で分かることは、
  • Tcl言語の部分は、<? と ?>で囲みます。 これはPHPと同じやり方です。
  • Tcl言語でHTMLを生成して出力するには、 mod_dtcl専用コマンドhputsを使います。
これだけ憶えておけば、ほらもう、サーバーサイドスクリプトの準備はOK。 あとは、mod_dtclはようするにTclなので、Tclでできることは何でも可能です。


ブラウザの中の環境変数とCGI変数(サンプル2)

mod_dtclの中で、CGIでよく使われるような環境変数を得るには、 hgetvarsコマンドを使います。 このコマンドは、

  • IPアドレスやブラウザバージョンなどの環境変数を Tcl配列変数ENVSに
  • HTMLフォームで渡されるフォーム変数を Tcl配列変数VARSに
  • ブラウザのクッキー(Cookie)にセットされている変数を Tcl配列変数COOKIESに
セットします。PHPのように最初から自動的にやってくれればいいのに… という向きもあるかもしれませんが、 起動時の無用な負荷をおさえるため、 このような仕様になったとマニュアルでは説明されています。

<HTML>
<HEAD><TITLE>mod_dtclのサンプル(環境変数を得る)</TITLE></HEAD>
<BODY>
<?
hputs "<H1>ブラウザの環境変数</H1>"
hgetvars
?>
<H2>定義されている環境変数</H2>
<TABLE BORDER=1>
<TR><TH>キー名</TD><TD>値</TD></TR>
<?
foreach e [lsort -dictionary -increasing [array names ENVS]] {
    hputs "<TR><TD>$e</TD><TD>$ENVS($e)</TD></TR>"
}
?>
</TR></TABLE>
<A HREF="index.html">戻る</A>
</BODY>
</HTML>

これで、例えばユーザーPCのIPアドレスや、 ユーザーが使っているブラウザの種類やバージョンを知ることができます。 実際にどんな情報が得られるのかは、 このHTMLならぬTTML文書を実際にロードして試してみてくださいね。


CGIぽくやっちゃってみよう(サンプル3)

それでは立て続けに、今度はHTMLフォーム変数にアクセスしてみます。 つまり、CGI(共通ゲートウェイ・インターフェース) の典型例としてよくあるものですが、 CGIと違って一度に一個ずつプロセスを作ったりしないので、 ずっと効率的に処理をさせることが可能です。 ちなみに完全に余談ですが、mod_dtcl のこうしたスクリプトの基本構造は PHPのそれにたいへんよく似ています。 ですから、PHPに親しんでいれば mod_dtcl はごく簡単に理解できますし、 もちろん逆も同様です。

<HTML>
<HEAD><TITLE>mod_dtclのサンプル(フォームを使う): 入力画面</TITLE></HEAD>
<BODY>
  <h1>足し算計算フォーム</h1>
  下の式に数字を入力して「OK」を押してください。

  <form action="sample3-result.ttml" method="POST">
  <input type="text" name=value1 size=10>
  +
  <input type="text" name=value2 size=10>
  <br>
  <input type="submit" value="OK">
  <input type="reset"  value="Reset">
  </form>

</BODY>
</HTML>

 まず、CGIでおなじみのフォーム画面です。 ここでは「sample3-query.html」と名付けました。 これはほんとにほんとの単なるフォームページなので、拡張子は.htmlでOKです。 このページをロードすると、2つの入力欄が現れるので、 ここに数値を入力してみてください。そんでそんで、OK(submit)ボタンを押すと、 フォームアクションで指定されている「sample3-result.ttml」 に飛びます。そう、ここでいよいよ .ttml の登場です。

<HTML>
<HEAD><TITLE>mod_dtclのサンプル(フォームを使う): 結果画面</TITLE></HEAD>
<BODY>
<h1>計算しました</h1>
<?
    set value1 $VARS(value1)
    set value2 $VARS(value2)
    hputs "$value1 + $value2 = [expr $value1 + $value2]<BR>"
    hputs "計算は終了しました。(たぶん)"
?>
</BODY>
</HTML>

 このように、CGIフォーム変数はTcl配列変数の $VARS(フォーム変数名) で参照できます。できますが…前の説明と食い違うところが? マニュアルに反して、この mod_dtcl 0.10.1 では、 CGIフォーム変数を取得するのに hgetvars コマンドを使う必要がないようです。 どちらが正しいのか? 気になって夜も眠れません。 気になる気になる木〜


自己完結型フォームスクリプト(ρテク)(サンプル4)

 はてさて、ここでPHPでもよく使われる基礎テクとして、 「自己完結型フォームスクリプト」(勝手にそう名付けました) が mod_dtcl でも使えるとゆーのをご紹介しておきましょう。 自己完結型とはとりもなおさずぬたもとりあえず、 1つのサーバーサイドスクリプトが、問い合わせ条件を入力するHTMLページと、 その結果を表示するHTMLページの両方を条件に応じて出力できるものです。 言葉で書くとなんじゃそれは、というところですが、 下の例をご覧頂ければなんとな〜くお分かりかと思います。

<html>
<?
  hgetvars
  if {! [info exists VARS(value1)]} {
?>
  <head><title>mod_dtclのサンプル(フォーム:問い合わせ)</title></head>
  <body bgcolor=white>
  <h1>足し算計算フォーム</h1>
  下の式に数字を入力して「OK」を押してください。

  <form action="sample4.ttml" method="POST">
  <input type="text" name=value1 size=10>
  +
  <input type="text" name=value2 size=10>
  <br>
  <input type="submit" value="OK">
  <input type="reset"  value="Reset">
  </form>

<? } else { ?>
  <head><title>mod_dtclのサンプル(フォーム:結果)</title></head>
  <body bgcolor=white>
  <h1>計算しました</h1>

  <?
    set value1 $VARS(value1)
    set value2 $VARS(value2)
    hputs "$value1 + $value2 = [expr $value1 + $value2]<BR>"
    hputs "計算は終了しました。(たぶん)"
  ?>

<? } ?>
</body>
</html>

 このスクリプト自体のURLは「sample4.ttml」です。 つまりフォームアクションのロード先「sample4.ttml」と全く同じです。 このローテクを支える原理は実に簡単で、この部分に集約されています。 すなわち集約的農業です。(関係ない関係ない)

  hgetvars
  if {! [info exists VARS(value1)]} {
なんのことはない、CGIフォーム変数が定義されていなければ入力画面、 定義されていれば結果表示画面を作っているだけですね。


HTML以外のコンテンツを生成する(サンプル5)

 mod_dtclで、HTML以外のコンテンツ、 例えばGIFやJPEGなどの画像データを生成することも可能です。どうするのか、というと…

<?
# sample5.ttml
# mod_dtclの簡単なサンプル(HTML以外のコンテンツを生成する)
headers type "image/jpeg"

set fin [open "/usr/share/pixmaps/backgrounds/space/gal_earth_moon.jpg" r]
fconfigure $fin -translation binary
set buf [read $fin]
close $fin

hputs $buf
?>

最初のポイントは、headers typeコマンドを使って、 いわゆる「Content-Type:」タイプを指定することです。

headers type "image/jpeg"
このheadersコマンドは、 それ以外のTclコマンドを実行する前に置かないといけないので注意が必要です。 で、実際の画像データはどうするかというと、
  • mod_dtclの専用コマンドincludeを使って、 画像などのバイナリファイルを取り込む。 (詳しくはマニュアルをチェックしてみてください)
  • ファイルを読むなどしてスクリプト内で生成し、 hputsコマンドで出力する。
上の例のように、hputsでバイナリデータを生成できるのもポイントです。 つまり、hputsコマンドは画像などのバイナリデータを正しく扱うことができるようになっています。

 さて、上のようなことを知ってしまったとするならば、 次にこんな例を試さないわけにはいきますまい。
つまり、mod_dtclスクリプトが画像データを生成できるのであれば、 <IMG SRC=>タグにmod_dtclスクリプトのURLも指定できるはずなのですが…

<HTML>
<HEAD><TITLE>mod_dtclの簡単なサンプル(生成した画像の埋め込み)</TITLE></HEAD>
<BODY>
<H1>画像にmod_dtclの出力を使う</H1>
この下に表示されます。<br>
<img src="sample5.ttml" width=200 height=200>
</BODY>
</HTML>

 このHTML文書自体は.ttmlではなく.htmlでOKですが、 IMGタグの中に不気味な拡張子のファイルが!なな、なんだこれわ!!(わざとらしく) この「sample5.ttml」というのはもちろん、その上の mod_dtclスクリプトです。 そして、ここにこうして載せているというのは、 うまくいったということですね。 これを応用すれば絵つきのカウンタなども作れると思います。


カウンタを作ってみる(サンプル6)

 と・い・う・わ・け・で、今度はカウンタですカウンタ。 ここで扱うのは、サーバーサイド・インクルード(SSI)による実装でよく見られた、 画像を使わずにHTML文書に直接「あなたは何人目の〜」と書き出すタイプのものです。

<HTML>
<HEAD><TITLE>mod_dtclの簡単なサンプル(ファイルの読み書き)</TITLE></HEAD>
<BODY>
<H1>カウンタも作れます</H1>

<?
namespace eval Counter {
  variable FileDir "/tmp"
  variable CountFile "/usr/local/apache/htdocs/momomo/sample6.count"

  proc count {} {
    variable FileDir
    variable CountFile

    # ロックディレクトリ作成
    set locksigndir [file join $FileDir "LOCK1"]
    set retry 5
    while {$retry > 0} {
      if {[file isdirectory $locksigndir]} {
        after 1000
        incr retry -1
      } else {
        break
      }
    }
    if {$retry <= 0} { hputs "read timeout<BR>"; exit ; }
    # あきらめて終了。

    # カウントファイルを読む
    if {[catch {set fin [open $CountFile r]}]} {
      set count 1
    } else {
      set count [gets $fin]
      close $fin
      incr count
    }

    # カウントファイルに書く
    set fout [open $CountFile w]
    puts $fout $count
    close $fout

    # ロックディレクトリ削除
    file delete $locksigndir

    return $count
  }
}
?>

いらっしゃいませ。あなたは
<?
set count [Counter::count]
hputs "$count"
?>
番目のお客様です。

</BODY>
</HTML>

 ちょっと長くなってしまったのは、ロック機構をどうやって作るべきか、 それとも全然気にしないのが果していいのか、どうなのかと、 そればかり考えていたある春の日、街頭で大音響で演奏するロックバンドを見ながら、 やはりロックテイストは必要なんだという結論になったためです。 (微妙にわかっとんのかコイツは?) そのロックテイストですけど、いちおう、この例ではロックディレクトリを作ってやっています。 ものの本によると、シンボリックリンクを使うのが、 時間的な隙間ができにくいよい方法だということです。 ただリンクだと可搬性がじゃっかん落ちてしまうというので、 ここではディレクトリにしています。


クッキーを使う(サンプル7)

 Webアプリケーションで困るのが、ユーザーの認証をどうするか、という点です。 つまり一度ログインしたユーザーが誰であるかを、 そのユーザーがリンクをたどってページを進んでいってもサーバー側で把握できればいいのですが、 HTTPは基本的に入力情報からのみ出力情報が決まる(ステートレスといいます) 世界なので、それに何らかの「状態」を持たせる処理が必要です。 大規模なWebアプリケーションサーバー製品ですと、「セッション管理」 としてそういったことをやらせる機能がサーバー側に備わっているものが多いのですが、 もっと簡単に、 サーバー側に特別な処理を持たせずにこの目的のために使われる機能がクッキー (Cookie)です。 mod_dtcl ではクッキーをとても簡単に使えるようにしてくれています。
早速使ってみます。

sample7-query.html(通常のログイン用HTMLフォーム)

<html>
<head><title>Cookie Sample Query</title></head>
<body bgcolor=white>

<form action="sample7-accept.ttml" method="post">
ユーザーID:
<input type=text size=15 name=userid><br>
名前:
<input type=text size=40 name=username><br>
<input type=submit value=OK>
<input type=reset value=Clear>
</form>

</body>
</html>

上のHTML文書は何の変哲もない、普通のログイン用HTMLフォームです。 入力欄 userid と username に入力された内容をクッキーに覚えこまそうという腹津森です。

sample7-accept.html(クッキーの作成)

<?

proc printHeader {} {
    hputs "<html>"
    hputs "<head><title>Cookie Sample Query - Accepted</title></head>"
    hputs "<body bgcolor=white>"
}

hgetvars
# セットされた変数を得る
set userid   [string trim $VARS(userid)]
set username [string trim $VARS(username)]

if {"$userid" == ""} {
    printHeader
    hputs "おおっと! ユーザーIDを入力してください。<br>"
    hputs "<a href=\"sample7-query.html\">戻る</a><br>"
} elseif {"$username" == ""} {
    printHeader
    hputs "おおっと! お名前を入力してください。<br>"
    hputs "<a href=\"sample7-query.html\">戻る</a><br>"
} else {
    headers setcookie -name c_LoginInfo -value [list $userid $username]
    printHeader
    hputs "いらっしゃいませ。$username (ID: $userid) さん。<br>"
    hputs "<a href=\"sample7-cookie.ttml\">こちら</a>をクリックしてください。<br>"
}

?>
</body>
</html>

この mod_dtcl スクリプトで、前のページのフォームに入力された値をクッキーに記憶させています。

    set userid   [string trim $VARS(userid)]
    set username [string trim $VARS(username)]
    headers setcookie -name c_LoginInfo -value [list $userid $username]
クッキーの作成はheaders setcookieコマンドで行います。 このコマンドはheaders typeコマンドと同様、 HTMLヘッダの出力より先に実行しないといけません。

sample7-cookie.html(クッキーの参照)

<html>
<head><title>Cookie Sample Query - Login Check?</title></head>
<body bgcolor=white>

<?
hgetvars
set userid   [lindex $COOKIES(c_LoginInfo) 0]
set username [lindex $COOKIES(c_LoginInfo) 1]

hputs "<font color=#700040>"
hputs "ようこそ、$username (ID: $userid) さん。<br>"
hputs "</font>"

?>
</body>
</html>

 そしてさらにリンクをたどった別のページから、 クッキーが覚えている内容を取り出してみます。 これはずっと前に少しだけ触れましたが、hgetvars コマンドを使うとセットされる、Tcl配列変数COOKIES を見ればOKです。キー値はheaders setcookie コマンドで -name で指定した値になります。

hgetvars
set userid   [lindex $COOKIES(c_LoginInfo) 0]
set username [lindex $COOKIES(c_LoginInfo) 1]

拡張レビュー分室 top
(first uploaded 2001/05/26 last updated 2002/05/20, MISUMI URANO)