医療情報学
システム工学
Web 開発の小技
ホーム / Web 開発の小技
* 更新情報
  • 2007.01.05 - 新設, はじめてのフィルタ

情報システムを構築する上での再利用可能な部品として Web は必要不可欠なものになっています。私もここ10年は Web 関連のシステムばかり作っています。システムの開発に十分な時間が割けないとき、システムを Web ベースにすることでプログラミング量を激減させることができますし、インストールやサポートも随分楽になります。これは、Web サーバや Web ブラウザとしてまさに State-of-Art で Solid なソフトウェアが提供されているからに他なりません。

Web サーバや Web ブラウザを使って、ちょっとだけダイナミックなページを作るのは簡単ですが、インタラクティブなアプリケーションと呼べるものを作るのはかなり大変です。ここでは、開発経験やいま勉強していることから Web 開発の小技(tips)についてまとめてみました。

スクリプト言語モジュール (mod_p*) を使えば CGI よりは高性能なサーバーサイドのプログラムを比較的手軽に作ることは出来ますが、既に出来上がっている手持ちの C のコードがある場合や、より高性能なものを作るとなるとサーバに組み込まれて動くモジュールを作ることになります。ここでは、Aapche バージョン 2.x 用のモジュールを作る際の、最初のステップについて解説します。

モジュールの開発途上ではデバッガ(gdb)で追いかけることになるので、マシンにインストールしてある httpd は使わずに、モジュール開発専用の httpd をソースからビルドすることにします。また、モジュール自身も httpd にスタティックにリンクすることにします。

Apache のソースを展開します。ここでは現時点での最新版である 2.2.3 を使います。FreeBSD のユーザであれば、/usr/ports/www/apache22make patch で展開し FreeBSD 特有のパッチを当てたものを入手すると良いでしょう。これを作業用のディレクトリにコピーします。

~$ cd src
src$ tar jzvf httpd-2.2.3.tar.bz2

    ...

src$ cd httpd-2.2.3/
httpd-2.2.3$ 

既に configure がありますが、今回はその前のステップ、つまり Autotools を動かして configure.in から Makefileconfigure を作るステップを実施します。apache のソース配布の中にはそのための buildconf というシェルスクリプトが入っています。もし、あなたの UN*X BOX の Autotools がバージョン名で修飾されていなければそのまま実行すれば良いですが、FreeBSD のようにコマンド名の後にバージョン番号が付いている場合は環境変数を通して指定します。

httpd-2.2.3$ env LIBTOOLIZE=libtoolize LIBTOOL=libtool AUTOCONF=autoconf259 AUTOHEADER=autoheader259 ./buildconf

    ...

httpd-2.2.3$ 

これで、configure ができました。configure を実行するさいですが、オプションはこんな感じ良いでしょう。もしシステム標準のCコンパイラを使うのであれば CC=gcc41 は不要です。MPM に prefork を指定しているのはデバッグを楽にするためです。マルチスレッド環境でのデバッグは面倒ですから。

httpd-2.2.3$ env CC=gcc41 CFLAGS='-ggdb -O0' ./configure --with-mpm=prefork --disable-cgi --disable-so

    ...

httpd-2.2.3$ make

    ...

httpd-2.2.3$

ビルドが完了すると、カレントディレクトリ(ソーストップディレクトリ)に実行ファイル (httpd) が出来上がります。実行はデバッガ (gdb) 上で行いますのでインストール (make install) はしません。

httpd を動かすためには必ず httpd.conf が必要になります。インストール前の httpd.confdocs/conf にあります。これをカレントディレクトリにコピーして、必要な部分を編集します。最低限の修正点は以下のようになります。

httpd.conf

ServerRoot "./"      デバッガから起動するので、カレントをルートにしておきます

Listen 8080          80 番はスーパーユーザじゃないと使えないし、既に走っているかもしれないし

#- @@LoadModule@@    コメントアウトします。

#- User %%WWWOWN%%   コメントアウトします。
#- Group %%WWWGRP%%

DocumentRoot "docs/docroot"   取り合えずは標準のドキュメントを使います

<Directory "docs/docroot">    DocumenRoot と一致させます。

    TypeConifg docs/conf/mime.types   修正します

これで準備完了。取り合えず動かしてみます。起動のコマンドラインは

httpd-2.2.3$ ./httpd -X -d . -f httpd.conf

gdb 上で動かす場合は

httpd-2.2.3$ gdb httpd
GNU gdb 6.1.1 [FreebSD]
    ...
(gdb) run -X -d . -f httpd.conf
Starting program: /mnt/work/WORK/DIOWave/NewViewerCore/apache2-module/httpd-2.2.3/httpd -X -d . -f httpd.conf


いよいよ自前のモジュールを作ります。ここではコンテンツハンドラではなく出力フィルタを作ります。まずは、全く何もフィルタしないフィルタを作ってみてフィルタの基本動作を学ぶことにします。

モジュールは modules ディレクトリの中で作ります。配布のモジュールと混じらないようにするため、自分専用のサブディクトリを作ることにします。名前は site にしておきます。フィルタのソースは mod_dumfilter.cpp、モジュール名は DUMFILTER にします。自分で作ったモジュールの存在を buildconf に知らせるため、config.m4 ファイルも作ります。

modules/site/mod_dumfilter.cpp
#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "apr_strings.h"
#include "apr_general.h"
#include "util_filter.h"
#include "apr_buckets.h"
#include "http_request.h"

普通は static にしますが、デバッガでブレークポイントを当てやすいようにグローバルにしてあります。
apr_status_t dumfilter_out_filter(ap_filter_t *f, apr_bucket_brigade *in_bb)
{
    const request_rec* const r = f->r;

    if (APR_BRIGADE_EMPTY(in_bb))
        return APR_SUCCESS;

    apr_bucket_brigade* const out_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);

    while (!APR_BRIGADE_EMPTY(in_bb)) {
        apr_bucket* const e = APR_BRIGADE_FIRST(in_bb);

        if (APR_BUCKET_IS_EOS(e)) {
            APR_BUCKET_REMOVE(e);
            APR_BRIGADE_INSERT_TAIL(out_bb, e);

            break;
        }

        if (APR_BUCKET_IS_FLUSH(e)) {
            apr_bucket_delete(e);

            apr_bucket* const bkt = apr_bucket_flush_create(f->c->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(out_bb, bkt);

            const apr_status_t rv = ap_pass_brigade(f->next, out_bb);
            if (rv != APR_SUCCESS)
                return rv;

            continue;
        }

        apr_size_t len;
        const char* data;
        apr_bucket_read(e, &data, &len, APR_BLOCK_READ);

        char* const buf = (char*)apr_palloc(r->pool, len);
        memcpy(buf, data, len);                      フィルタ処理の実体はこの辺で実現します。

        apr_bucket* const b = apr_bucket_pool_create(buf, len, r->pool, f->c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(out_bb, b);
		
        apr_bucket_delete(e);
    }

    ap_remove_output_filter(f);
    apr_brigade_cleanup(in_bb);
    return ap_pass_brigade(f->next, out_bb);
}

普通は static にしますが、デバッガでブレークポイントを当てやすいようにグローバルにしてあります。
void dumfilter_register_hooks(apr_pool_t *p)
{
    ap_register_output_filter("DUMFILTER", dumfilter_out_filter, NULL, AP_FTYPE_RESOURCE);
}

module AP_MODULE_DECLARE_DATA dumfilter_module = {
    STANDARD20_MODULE_STUFF,
    NULL,                         /* dir config creater */
    NULL,                         /* dir merger --- default is to override */
    NULL,                         /* server config */
    NULL,                         /* merge server config */
    NULL,                         /* command table */
    dumfilter_register_hooks      /* register hooks */
};
modules/site/config.m4
APACHE_MODPATH_INIT(site)
APACHE_MODULE(dumfilter, Dummy Filter, , , static, [
  APR_SETVAR(MOD_DUMFILTER_LDADD, [-lstdc++])    C++ をつかっているのでランタイムライブラリが必要になります。
])
APACHE_MODPATH_FINISH

ただし、このままだと素直には行きません。なぜなら、このモジュールは C++ のソースですが、Apache の中では C++ のコードは一切使われていないのでビルドシステムが対応していないのです。そこで、ビルドシステムのソースである configure.in にちょっとだけパッチします。

configure.in
AC_PROG_CC
AC_PROG_CPP
AC_PROG_CXX     これを追加して C++ に対応させます

また、configure を実行する際にも C++ 関連の環境変数を追加します。

httpd-2.2.3$ env CXX=g++41 CXXFLAGS='-ggdb -O0' CC=gcc41 CFLAGS='-ggdb -O0' ./configure --with-mpm=prefork --disable-cgi --disable-so

このフィルタを有効にするため、httpd.conf に以下のセクションを追加します。

<Directory "docs/docroot/file">
  SetOutputFilter DUMFILTER

  Order Deny,Allow
  Allow from all
</Directory>

これで、ダミーのフィルタの完成です。デバッガの中で動かして見ましょう。