Apacheモジュール開発記 その1

改めましてこんにちはxorです。その0を書いた後、あらぬトラブルに見舞われ、その解決に時間を取られておりました。情けない。

おさらい

さて、その0にて必要最低限のApacheモジュールの作り方を説明したが、ここで改めて簡単な説明をしておきましょう。

  1. apxs2が使える環境にする
  2. $ apxs2 -n モジュール名 -g
  3. $ cd モジュール名
    • ディレクトリの中にはMakefileとmod_モジュール名.cとmodules.mkというファイルがあるはず
  4. ここから下はroot権限でやらないと失敗すると思うのでsu
  5. # apxs2 -i -a -c モジュール名.c
  6. Apacheの設定ファイルにモジュールのことを記述
    • Debianの場合
      1. /etc/apache2/mods-availableに「モジュール名.conf」と「モジュール名.load」というファイルを作る
      2. 「モジュール名.conf」には「SetHandler モジュール名
      3. 「モジュール名.load」には「LoadModule モジュール名_module /usr/lib/apache2/modules/mod_モジュール名.so」と書いておく
      4. /etc/apache2/mods-enabledに、先ほどの「モジュール名.conf」と「モジュール名.load」のシンボリックリンクを作成する
    • 他の場合もだいたい同じだと信じたい
  7. Apacheを再起動 $ apache2ctl restart

こうすると、「http://localhost/ディレクトリ名/」にアクセスすることで「The sample page from mod_モジュール名.c」と書かれたページが表示されるはずです。ここでは「モジュール名」とか「ディレクトリ名」とか書きましたが、どちらも任意のものを使ってください。例えばモジュール名が「gvr」なら、/etc/apache2/mods-enabledにはgvr.loadとgvr.confという名前のシンボリックリンクができていると思われます。こんな感じで、適宜置き換えて読んでください。…というのもかなりきついので、ここではmod_testというモジュールを作ります。モジュール名は「test」です。

あと、モジュール名とディレクトリ名は別に一致してなくても問題はありません。まぁ、今回は混乱を避けるために一致させておきます。そんなわけでディレクトリ名も「test」です。できあがったモジュールはhttp://localhost/testにて動作確認できます。

基本的な原理

さて、それでは早速apxs2によって生成したmod_test.cの中身を見てみましょう。こんな感じだと思います。

/* 
**  mod_test.c -- Apache sample test module
**  [Autogenerated via ``apxs -n test -g'']
**
**  To play with this sample module first compile it into a
**  DSO file and install it into Apache's modules directory 
**  by running:
**
**    $ apxs -c -i mod_test.c
**
**  Then activate it in Apache's apache2.conf file for instance
**  for the URL /test in as follows:
**
**    #   apache2.conf
**    LoadModule test_module modules/mod_test.so
**    <Location /test>
**    SetHandler test
**    </Location>
**
**  Then after restarting Apache via
**
**    $ apachectl restart
**
**  you immediately can request the URL /test and watch for the
**  output of this module. This can be achieved for instance via:
**
**    $ lynx -mime_header http://localhost/test 
**
**  The output should be similar to the following one:
**
**    HTTP/1.1 200 OK
**    Date: Tue, 31 Mar 1998 14:42:22 GMT
**    Server: Apache/1.3.4 (Unix)
**    Connection: close
**    Content-Type: text/html
**  
**    The sample page from mod_test.c
*/ 

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "ap_config.h"

/* The sample content handler */
static int test_handler(request_rec *r)
{
    if (strcmp(r->handler, "test")) {
        return DECLINED;
    }
    r->content_type = "text/html";      

    if (!r->header_only)
        ap_rputs("The sample page from mod_test.c\n", r);
    return OK;
}

static void test_register_hooks(apr_pool_t *p)
{
    ap_hook_handler(test_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA test_module = {
    STANDARD20_MODULE_STUFF, 
    NULL,                  /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    NULL,                  /* table of config file commands       */
    test_register_hooks  /* register hooks                      */
};

test_handlerという関数とtest_register_hooksという関数がありますね。しかし、お馴染みのmain関数がありません。

なぜなら、Apacheのモジュールは単独で動作するアプリではなく、Apacheに寄生するものだからです。つまり、main関数のように実行して終わり、では無いのです。代わりに必要な処理は

くらいでしょうか。いきなり浮世離れしてますね。

ですが、ソースコード中に出てくる文字列をよく見てみると、Webアプリを作ったことのある人ならお馴染みのキーワードが出てきていると思われます。例えば「r->content_type = "text/html"」とか「return OK」とか。

弄ってみる

試しにいろいろ弄ってみましょう。この「OK」って、実は「200 OK」のことです。その証拠に、この「return OK」を「return HTTP_NOT_FOUND」に書き換えてみてください。http://localhost/testにアクセスすると404 Not Foundが返ってくるでしょう。他にもHTTP_FORBIDDENとかHTTP_METHOD_NOT_ALLOWEDとかHTTP_GONEとか、いろいろなメッセージが用意されています。全部見てみたい方はhttp://httpd.apache.org/dev/apidoc/apidoc_HTTP_ACCEPTED.htmlを御覧ください。ただ、残念ながらRFC2324で定義された418 I'm a teapotは用意されていない…(まぁアレはHTTPじゃなくてHTCPCPなのだが)。

次に、content_typeを弄ってみましょう。初期値はtext/htmlですが、これをtext/plainにすれば全てがプレーンテキストとして表示されるはずです。application/xxにすれば「どのアプリケーションで開きますか?」とブラウザが訊いてくるかもしれません。まぁ、好きなMIMEタイプを書いて遊んでみてください。

最後に「The sample page from mod_test.c」ですね。ここも、書き換えればその通りに出力されます。また、ここではap_rputs関数を1回しか呼んでいませんが、もちろん2回でも3回でも呼んでいただいてOKです。例えば1回目で「jinsei」、2回目で「makegumi」を引数にして2回呼んだ場合、「jinseimakegumi」と出力されます。また、rputs関数の代わりにrprintf関数を使うこともできます。これはその名の通り、printf風のフォーマットを使って出力ができる関数です。例えば「ap_rputs("test",r)」の代わりに「ap_rprintf(r,"test")」と書いても、同じく「test」と出力されます。さらにap_rprintfはprintfと同等の機能を持つので、例えば「ap_rprintf(r,"add:%02d+%04d=%d",5,12,5+12)」と書けば「add:05+0012=17」と出力されます。

こんな感じで、雛型だけでも弄りまくってみてください。