apache 2.0用のモジュール作成

自分向けの備忘録。

準備

Apacheの開発用パッケージが必要。CentOSならば、

yum install httpd.devel

でインストールできる。

プロジェクトの作成

apxsというツールを利用してひな形を作成することができる。次の例は、mod_helloworldを作成する。

apxs -g -n helloworld

ビルド

apxsの作成したMakefileを利用する場合、ビルドは簡単である。

make CC=gcc CXX=c++

ただし、実際には、g++を利用した場合、リンク時の問題を避けるために、

make CC=g++ CXX=g++

とした方が良いと考えられる。

インストール

特に難しいことに気を遣う必要がなければ、apxsの作成したMakefileで行うことができる。

make install

ただし、設定ファイルは自分で作成する必要がある。CentOSの場合には、/etc/httpd/conf.dの下に、helloworld.confのような名前で次のようなファイルを作成する。

LoadModule helloworld_module modules/mod_helloworld.so

<Location /helloworld>
  SetHandler helloworld
  SetMessage Hello, everyone!
</Location>

LoadModuleの第一パラメータはモジュール名だが、これについては、次のコーディングを参照のこと。

コーディング

モジュール名

LoadModuleの第一パラメータ、モジュール名は、ソースコード中で、

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

のように定義されている変数の名前である。

helloworld_create_per_dir_config関数

この関数は、ディレクトリごとの設定、つまり、Locationあるいは、Directoryによって設定された設定を保存する領域を初期化する関数である。構造体の構造(設計)および、そのインスタンス作成は完全にユーザーに委ねられている。
helloworldでは、表示メッセージを保持するために次のような構造体を定義している。

typedef struct _helloworld_per_dir_config
{
  const char* message; // 表示するメッセージ
}
helloworld_per_dir_config;

そして、helloworld_create_per_dir_config関数は、この構造体のためのメモリ確保、初期化を行う。実際の実装は次のようになる。

static void* helloworld_create_per_dir_config(apr_pool_t* p, char* arg)
{
  helloworld_per_dir_config* conf;
  conf = (helloworld_per_dir_config*)apr_palloc(p, sizeof(helloworld_per_dir_config));
  conf->message = ""; // 無難な初期値
}

ここでは、mallocなどではなく、aprが提供するapr_pallocを利用する。この場合、エラー復帰時などにメモリを解放し忘れた場合にも、apacheがプール(apr_pool_tによって管理されている)解放時に自動的に解放を行ってくれるため、メモリリークを防ぐことができる。

設定コマンド

また、Locationブロックの中で利用される設定コマンドは、上記のmodule構造体に指定されているhelloworld_commandsによって定義されている。

static const command_rec helloworld_commands[] = {
  {
    "SetMessage",
    set_message, // コマンドを処理する関数
    NULL,
    OR_ALL,
    TAKE1, // 一つのパラメータをとる
    "Specify message!"
  },
  {NULL}
};
set_message関数

set_message関数の定義は次のようになる

static const char* set_message(cmd_parms* parms, void* pthis, char* arg)
{
  helloworld_per_dir_config* conf = (helloworld_per_dir_config*)pthis;
  conf->message = apr_pstrdup(parms->pool, arg;
}

ここで利用しているhelloworld_per_dir_configは、上で定義したものである。
また、apr_pstrdupは、strdupと同等の処理を行うが、プールからのメモリ割り当てを行う。

helloworld_register_hooks関数

helloworld_register_hooks関数は、実際の処理を行う関数群(フック)を登録する関数である。実際の実装は次のようになる。

static void helloworld_register_hooks(apr_pool_t *p)
{
  ap_hook_handler(helloworld_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
helloworld_handler関数

helloworld_register_hooks関数中で、APR_HOOK_MIDDLEに対して関連づけられているhelloworld_handler関数は、実際のリクエストに対するレスポンス出力処理を行う。実際の実装は次のようになる。

static int helloworld_handler(request_rec *r)
{
  if (strcmp(r->handler, "helloworld")) {
    return DECLINED;
  }
  r->content_type = "text/html";
 
  if (r->header_only) // HEAD処理の場合には本文は送らない
    return OK;

  // 設定の構造体を取得 
  helloworld_config* conf = (helloworld_config*)get_module_config(p_rec->per_dir_config, &helloworld);

  // 出力
  apr_rprintf(r, "<h1>%s</h1>\n", conf->message);

  return OK;
}