読者です 読者をやめる 読者になる 読者になる

中年engineerの独り言 - crumbjp

LinuxとApacheの憂鬱

最小構成のアパッチモジュール解説

今回は、実用的モジュールの最小構成として次の解説をします。

  • httpd.confからモジュールに設定情報を与える方法。(command_rec)
  • 必要なリクエストのみモジュールを動作させる方法。(SetHandler)
  • ログを出力する方法。

細かいそれぞれの設定のバリエーションに関しては
次回一覧にする予定なので今回は典型的な例だけ説明します。

説明の為、前回の日記
アパッチモジュールの初歩・その壱 - LinuxとApacheの憂鬱
で生成したソースを少し改造します。

mod_mytest.c
/* プロトタイプ宣言を追加 */

#include "httpd.h"
/* ログ出力用ヘッダ(追加) */
#include "http_log.h"
#include "http_config.h"
#include "http_protocol.h"
#include "ap_config.h"

/* プロトタイプ宣言を追加(追加) */
module AP_MODULE_DECLARE_DATA mytest_module;

/* モジュール設定情報(追加) */
struct mytest_config {
  char  * message;
}mytest_config;

/* 設定情報の生成・初期化(追加) */
static void * create_per_dir_config (apr_pool_t *pool, char *arg)
{
  void * buf = apr_pcalloc(pool, sizeof(mytest_config));
  struct mytest_config *cfg = (struct mytest_config*)buf;
  // default value
  cfg->message    = "The sample page by mod_mytest.c";
  return buf;
}

/* ハンドラ本体 */
static int mytest_handler(request_rec *r)
{
    if (strcmp(r->handler, "mytest")) {
        return DECLINED;
    }
    /* 設定情報取得(追加) */
    struct mytest_config *cfg = (struct mytest_config*)ap_get_module_config(r->per_dir_config, &mytest_module);
    r->content_type = "text/html";      

    /* 設定を出力(変更) */
    if (!r->header_only) {
        ap_rputs(cfg->message, r);
        /* ログ出力 */
        ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "request : %s",r->uri);
    }
    return OK;
}

/* ハンドラ登録 */
static void mytest_register_hooks(apr_pool_t *p)
{
    ap_hook_handler(mytest_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

/* 設定情報読み込み(追加) */
static const char *cmd_mytest_message (cmd_parms *parms, void *mconfig, char *arg){
  struct mytest_config *cfg = (struct mytest_config*)mconfig;
  cfg->message = apr_pstrdup(parms->pool,arg);
  return 0;
}

/* 設定情報フック定義(追加) */
typedef const char *(*CMD_HAND_TYPE) (cmd_parms*,void*);
static const command_rec mytest_cmds[] = {
  {
    "MytestMessage",                    /* ディレクティブ名 */
    (CMD_HAND_TYPE)cmd_mytest_message,  /* コールバック関数 */
    0,                                  /* 0で良い */
    OR_ALL,                             /* ディレクティブが現れるべき位置(何処でもの意) */
    TAKE1,                           /* 引数の数(一つの意) */
    "Custom message."                   /* エラーメッセージ(特に気にしなくても良い) */
  },
  {0},
};

/* モジュール・フック定義(create_per_dir_config,mytest_cmds,追加) */
module AP_MODULE_DECLARE_DATA mytest_module = {
    STANDARD20_MODULE_STUFF, 
    create_per_dir_config, /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    mytest_cmds,           /* table of config file commands       */
    mytest_register_hooks  /* register hooks                      */
};

httpd.confでは以下の内容を設定します。

LoadModule mytest_module modules/mod_mytest.so
<Location /mytest1>
    SetHandler mytest
</Location>
<Location /mytest2>
    SetHandler mytest
    MytestMessage "Here is mytest2 !"
</Location>
<Location /mytest3>
    SetHandler mytest
    MytestMessage "Here is mytest3 !"
</Location>

これでURL毎に違うメッセージが出力されるハズです。
さて、コードの説明に入ります。

モリー管理

基本的にモジュール内ではmalloc等によるメモリー確保を行いません。
アパッチは優れたメモリー管理機構が備わっているのでそちらを利用します。
アパッチの確保したメモリーapr_pool_tという型で定義されており
必要な箇所で必要なスコープをもつメモリプールが利用出来る様になっています。
詳細は次回以降に掲載する予定です。

httpd.confからモジュールに設定情報を与える

httpd.conf は 以下の部分に分けて処理されます。

server
VirtualHost単位(+非VirtualHost領域)
directory
Location,Directory 単位
merge server
VirtualHostを跨いだ設定をマージ
merge directory
VirtualHostを跨いだ設定をマージ

今回はdirectory単位だけを解説します。
これだけで大抵のモジュールは作れるはずです。

必要な手続きは4点

  • 設定情報の保存領域の確保+初期化のコールバック
  • httpd.confのディレクティブの定義
    • 名前
    • 読み込みコールバック
    • ディレクティブが現れるべき位置
    • ディレクティブ引数の数
  • ディレクティブ読み込みコールバック
  • 設定情報を利用する
設定情報の保存領域の確保+初期化のコールバック

登録はコールバック名を指定するだけです。
これによりhttpd.confに設定したあらゆるディレクトリからコールバックが呼ばれます。

module AP_MODULE_DECLARE_DATA mytest_module = {
  :
  create_per_dir_config, /* create per-dir    config structures */
  :
};

特別なことをしない限り常にこの形でOK!

static void * create_per_dir_config (apr_pool_t *pool, char *arg)
{
  void * buf = apr_pcalloc(pool, sizeof(mytest_config));
  struct mytest_config *cfg = (struct mytest_config*)buf;
  // default value
  cfg->message    = "The sample page by mod_mytest.c";
  return buf;
}

滅多に気にする必要はありませんがargはディレクトリ名です。
例えば以下の様に指定した場所ではarg="/mytest"となります。

 <Location /mytest>
 </Location>
httpd.confのディレクティブの定義

command_rec 構造体の配列で定義します。


ディレクティブの読み出しはコールバックにより行われます。


ディレクトリ単位の設定情報領域を管理しリクエスト毎にそれを特定して処理を行うのは大変ですが
全てアパッチ自身がサポートしてくれます。


我々は読み込み時にはコールバックに渡された領域に情報を書き込み
リクエストがあった場合は設定情報を取得する関数を呼び出せば良いのです。


command_rec 自体の設定はココ

module AP_MODULE_DECLARE_DATA mytest_module = {
  :
    mytest_cmds,           /* table of config file commands       */
  :
};
ディレクティブ毎の設定

ディレクティブの設定はコメントの通り。

  {
    "MytestMessage",                    /* ディレクティブ名 */
    (CMD_HAND_TYPE)cmd_mytest_message,  /* コールバック関数 */
    0,                                  /* 0で良い */
    OR_ALL,                             /* ディレクティブが現れるべき位置(何処でもの意) */
    TAKE1,                           /* 引数の数(一つの意) */
    "Custom message."                   /* エラーメッセージ(特に気にしなくても良い) */
  },
ディレクティブ読み込みコールバック

ディレクティブが指定されたディレクトリ用の領域が渡されるのでそこに書き込むだけです。

static const char *cmd_mytest_message (cmd_parms *parms, void *mconfig, char *arg){
  struct mytest_config *cfg = (struct mytest_config*)mconfig;
  cfg->message = apr_pstrdup(parms->pool,arg);
  return 0;
}
設定情報を利用する

設定情報を呼び出すのはたった一行です。

static int mytest_handler(request_rec *r)
{
    :
    struct mytest_config *cfg = (struct mytest_config*)ap_get_module_config(r->per_dir_config, &mytest_module);
    :
}

リクエストのあったディレクトリ毎の情報が取得できます。

必要なリクエストのみモジュールを動作させる方法(SetHandler)

ap_hook_handler()によって登録されたハンドラは全てのリクエストで呼ばれてしまうので
必要なリクエストを判定する為にSetHandlerディレクティブを使用します。

LoadModule mytest_module modules/mod_mytest.so
<Location /mytest>
    SetHandler mytest
</Location>

この値はrequest_rec構造体のhandlerによって渡されるのでハンドラ内で判定できます。

static int mytest_handler(request_rec *r)
{
    if (strcmp(r->handler, "mytest")) {
        return DECLINED;
    }
    :

ハンドラの戻り値にDECLINEDを指定した場合
アパッチはこのハンドラが存在しなかった場合と同じ動作をします。
つまり次のハンドラへ処理を渡す事になります。

ログを出力する方法

アパッチのログ機構には以下ものがあります。

  • ErrorLog
  • CustomLog

通常モジュールが考慮する必要があるのはErrorLogです
またErrorLogに出力する為には以下の方法があります。

  • ap_log_error()系の関数を使う。
  • 標準エラー(stderr)に出力する。(デバッグ時に使うと便利)

ココではap_log_error()系解説を行います。
ErrorLogではログレベルが設定できLogLevelディレクティブにより動的に出力を制御できます。
レベルのバリエーションは以下の通り。

  • APLOG_EMERG
  • APLOG_ALERT
  • APLOG_CRIT
  • APLOG_ERR
  • APLOG_WARNING
  • APLOG_NOTICE
  • APLOG_INFO
  • APLOG_DEBUG

ap_log_error()系は幾つかありますが通常ハンドラからのエラー出力は
request_rec * を引数に取るap_log_rerror()で行います。

ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "request : %s",r->uri);

printf()と同じ感覚でフォーマット制御も出来るので便利ですね。

次回

今回省略したそれぞれの設定値のバリエーションの一覧などを紹介して行きたいと思います。