httpd

February 04, 2009

Apache Module POSTパラメータの処理 - 改訂版

2008/11/28の記事で掲載したサンプルコードが、mod_sslを利用した場合に動作しない、という現象が発生しました。

 調査を進めたところ、この現象はmod_sslの実装云々というよりは、私がbucketの種類について理解していなかったことが原因であることがわかりました。

bucketにはデータの実体がファイルであるものや、ソケットであるものなど様々なタイプがあります。

bucketタイプのうち、データをメモリ上に保持しているものとして、以下のタイプがあります。
MMAP bucket type
データの実体がmmapされたファイルに格納されている
  
POOL bucket type
データの実体がメモリプール領域に格納されている
HEAP bucket type
データの実体がヒープメモリ領域に格納されている
IMMORTAL bucket type
データの実体がスタティック領域など、永続的な領域に格納されている
TRANSIENT bucket type
データの実体がスタック領域など、一時的な領域に格納されている
apacheのデフォルトの状態では、ap_get_brigadeで取得したbucket brigade内でデータを保持していたのはHEAP bucketだったのですが、mod_sslを利用した場合、データはTRANSIENT bucketとして保持されていました。

TRANSIENT bucketはデータの実体がメモリ上の一時的な領域に格納されていることを示すため、bucketのデータを処理する側がその都度データ内容を複製する必要があるのですが、これを理解していませんでした。

この点を踏まえて、改訂したサンプルコードを以下に掲載します。parse_form_from_POST関数内のbucketを処理するループで、bucket毎にデータを複製した上で詰め替えを行うようにしました。
#include <ctype.h>
#include <string.h>
#include <httpd.h>
#include <http_config.h>
#include <http_protocol.h>
#include <http_log.h>
#include <apr_hash.h>
#include <apr_strings.h>

#define MAX_SIZE 1048576

static void blog_hooks(apr_pool_t *pool);
static int blog_handler(request_rec *r);

module AP_MODULE_DECLARE_DATA blog_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    blog_hooks
};

static void blog_hooks(apr_pool_t *pool)
{
    ap_hook_handler(blog_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

static apr_hash_t *parse_form_from_string(request_rec *r, char *args)
{
    apr_hash_t *form;
    apr_array_header_t *values;
    char *pair;
    char *eq;
    const char *delim = "&";
    char *last;
    if (args == NULL) {
        return NULL;
    }

    form = apr_hash_make(r->pool);

    for (pair = apr_strtok(args, delim, &last);
         pair != NULL;
         pair = apr_strtok(NULL, delim, &last)){
        for (eq = pair; *eq; ++eq){
            if(*eq == '+'){
                *eq = ' ';
            }
        }
        eq = strchr(pair, '=');
        if (eq) {
            *eq++ = '\0';
            ap_unescape_url(pair);
            ap_unescape_url(eq);
        }
        else {
            eq = "";
            ap_unescape_url(pair);
        }
        values = apr_hash_get(form, pair, APR_HASH_KEY_STRING);
        if (values == NULL) {
            values = apr_array_make(r->pool, 1, sizeof(char*));
            apr_hash_set(form, pair, APR_HASH_KEY_STRING, values);
        }
        *((char **)apr_array_push(values)) = apr_pstrdup(r->pool, eq);
    }

    return form;
}

static int parse_form_from_POST(request_rec *r, apr_hash_t **form)
{
    int bytes, eos;
    apr_size_t count;
    apr_status_t rv;
    apr_bucket_brigade *bb;
    apr_bucket_brigade *bbin;
    char *buf;
    apr_bucket *b;
    apr_bucket *nextb;
    const char *clen = apr_table_get(r->headers_in, "Content-Length");

    if (clen != NULL){
        bytes = strtol(clen, NULL, 0);
        if (bytes >= MAX_SIZE) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "Request too big (%d bytes; limit %d)",
                          bytes, MAX_SIZE);
            return HTTP_REQUEST_ENTITY_TOO_LARGE;
        }
    }
    else {
        bytes = MAX_SIZE;
    }
    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    count = 0;
    eos = 0;
    do {
        rv = ap_get_brigade(r->input_filters, bbin, AP_MODE_READBYTES,
                            APR_BLOCK_READ, bytes);

        if (rv != APR_SUCCESS) {
            return HTTP_INTERNAL_SERVER_ERROR;
        }
        for (b = APR_BRIGADE_FIRST(bbin);
             b != APR_BRIGADE_SENTINEL(bbin);
             b = nextb) {
            nextb = APR_BUCKET_NEXT(b);
            if (APR_BUCKET_IS_EOS(b)) {
                eos = 1;
            }
            if (!APR_BUCKET_IS_METADATA(b)) {
                if (b->length != (apr_size_t)(-1)) {
                    count += b->length;
                    if (count > MAX_SIZE) {
                        apr_bucket_delete(b);
                    }
                }
            }
            if (count <= MAX_SIZE) {
                if ( b->length != 0 ) {
                    /*データを複製して新たなbucketに詰め替え*/
                    const char *data;
                    apr_size_t len;
                    rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
                    if (rv != APR_SUCCESS){
                        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                                      "Error  apr_bucket_read.");
                        return HTTP_INTERNAL_SERVER_ERROR;
                    }
                    if (len > 0){
                        const char *data_copied = apr_pmemdup(r->pool, data, len);
                        apr_bucket *bucket_copied =
                          apr_bucket_transient_create(data_copied,
                                                      len,
                                             r->connection->bucket_alloc);
                        apr_bucket_delete(b);
                        APR_BRIGADE_INSERT_TAIL(bb, bucket_copied);
                    }
                }
            }
        }
        apr_brigade_cleanup(bbin);
    }while (!eos);
    if (count > MAX_SIZE) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                      "Request too big (%d bytes; limit %d)",
                      count, MAX_SIZE);
        return HTTP_REQUEST_ENTITY_TOO_LARGE;
    }
    buf = apr_palloc(r->pool, count+1);
    rv = apr_brigade_flatten(bb, buf, &count);
    if (rv != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                      "Error (flatten) reading form data");
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    buf[count] = '\0';
    *form = parse_form_from_string(r, buf);
    return OK;
}

static int blog_handler(request_rec *r)
{
    int rv = 0;
    apr_hash_t *formdata = NULL;

    if (!r->handler || (strcmp(r->handler, "blog") != 0)) {
        return DECLINED ;
    }

    if (r->method_number != M_POST) {
        return HTTP_METHOD_NOT_ALLOWED;
    }

    const char * ctype = apr_table_get(r->headers_in, "Content-Type");
    if (ctype && (strcasecmp(ctype,
                             "application/x-www-form-urlencoded") == 0)){
        rv =parse_form_from_POST(r, &formdata);
        if (rv != OK){
            return rv;
        }
    }

    ap_set_content_type(r, "text/html;charset=utf-8");
    ap_rputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">"
             "<html><head><title>Apache Module</title></head>"
             "<body>"
             "<p>This is my Apache module!</p>",
             r);

    if (formdata == NULL) {
        ap_rputs("<p>No form data found.</p>", r);
    }
    else {
        apr_array_header_t *arr;
        char *key, *p;
        apr_ssize_t klen;
        apr_hash_index_t *index;
        char **val_ptr;

        ap_rprintf(r, "<h2>Form data supplied by method %s </h2><dl>",
                   r->method);

        for (index = apr_hash_first(r->pool, formdata);
             index != NULL;
             index = apr_hash_next(index)){
            apr_hash_this(index,
                          (const void **)&key,
                          &klen,
                          (void **)&arr);
            ap_rprintf(r, "<dt>%s</dt>", ap_escape_html(r->pool, key));
            for (val_ptr = apr_array_pop(arr);
                 val_ptr != NULL;
                 val_ptr = apr_array_pop(arr)){
                char *val = *val_ptr;
                ap_rprintf(r, "<dd>%s</dd>",
                           ap_escape_html(r->pool, val));
            }
        }
        ap_rputs("</dl>", r);
    }
    ap_rputs("</body></html>", r);
    return OK;
}


matssaku at 00:12|PermalinkComments(1)TrackBack(0)clip!

November 27, 2008

Apache Moduleをつくる - POSTパラメータの処理 -

mod_sslと同時に利用した場合、正常に動作しなかったので、改訂版をこちら に記載しました。

GETパラメータの処理に続いて、POSTパラメータの処理について説明します。

前回と同じく、このエントリの内容、およびサンプルコードの多くの部分で、Apache Modules Book(リンクはAmazonアソシエイト)を引用しています。

また、アリエルネットワークの井上誠一郎さんによるブログ「Apacheの話」も参考にしています。

Apache2系では、POSTパラメータは、bucket brigadeと呼ばれる抽象化されたデータ構造の形で取得することができます。

bucket brigadebucketがリング状に連なった構造であり、各bucketがデータを少しずつ保持しています。

bucketは、入力ストリームを抽象化したデータ構造です。

POSTデータの処理手順は、
(1)bucket brigadeデータ構造をアロケート
(2)request_recinput_filtersからbucket brigadeを取得
(3)取得したbucket brigadeの各bucketからデータを読み込む
(4)(2)、(3)EOS bucketが出現するまで繰り返す
という流れになります。EOS bucketとは、データの終りを示す特殊なbucketです。

サンプルコードは以下のとおりです。
static int parse_form_from_POST(request_rec *r, apr_hash_t **form)
{
    int bytes, eos;
    apr_size_t count;
    apr_status_t rv;
    apr_bucket_brigade *bb;
    apr_bucket_brigade *bbin;
    char *buf;
    apr_bucket *b;
    apr_bucket *nextb;
    const char *clen = apr_table_get(r->headers_in, "Content-Length");

    if (clen != NULL){
        bytes = strtol(clen, NULL, 0);
        if (bytes >= MAX_SIZE) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "Request too big (%d bytes; limit %d)",
                          bytes, MAX_SIZE);
            return HTTP_REQUEST_ENTITY_TOO_LARGE;
        }
    }
    else {
        bytes = MAX_SIZE;
    }
    /* 読み込みデータ格納用bucket brigadeのアロケート */
    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    /* POSTデータ取得用bucket brigadeのアロケート */
    bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    count = 0;
    eos = 0;
    do {
        /* input_filters からPOSTデータのbucket brigadeを取得 */
        rv = ap_get_brigade(r->input_filters, bbin, AP_MODE_READBYTES,
                            APR_BLOCK_READ, bytes);

        if (rv != APR_SUCCESS) {
            return HTTP_INTERNAL_SERVER_ERROR;
        }
        /* bucket brigadeの中の各bucketを辿るループ */
        for (b = APR_BRIGADE_FIRST(bbin);
             b != APR_BRIGADE_SENTINEL(bbin);
             b = nextb) {
            nextb = APR_BUCKET_NEXT(b);
            /* EOS bucketかどうか */
            if (APR_BUCKET_IS_EOS(b)) {
                eos = 1;
            }
            if (!APR_BUCKET_IS_METADATA(b)) {
                if (b->length != (apr_size_t)(-1)) {
                    count += b->length;
                    if (count > MAX_SIZE) {
                        apr_bucket_delete(b);
                    }
                }
            }
            if (count <= MAX_SIZE) {
                /* 
                   MAX_SIZEを超えていなかったら、
                 読み込みデータ格納用のbrigadeに移し替える
                */
                APR_BUCKET_REMOVE(b);
                APR_BRIGADE_INSERT_TAIL(bb, b);
            }
        }
    }while (!eos);
    if (count > MAX_SIZE) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                      "Request too big (%d bytes; limit %d)",
                      count, MAX_SIZE);
        return HTTP_REQUEST_ENTITY_TOO_LARGE;
    }
    buf = apr_palloc(r->pool, count+1);
    /* 読み込みデータ格納用brigadeをchar[]に展開 */
    rv = apr_brigade_flatten(bb, buf, &count);
    if (rv != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                      "Error (flatten) reading form data");
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    buf[count] = '\0';
    /* 取得データをハッシュマップにパース */
    *form = parse_form_from_string(r, buf);
    return OK;
}
この関数では、input_filtersから取得した入力データbucket brigade(bbin)のうち、必要なbucketを読み込みデータ格納用のbucket brigade(bb)に移し替えた上で、char[]に展開する、という方法を採っています。入力データのサイズチェックのために、少し複雑なコードになっています。

この関数を利用してPOSTパラメータを表示する、モジュール全体のサンプルコードは以下のようになります。なお、Content-Typeapplication/x-www-form-urlencodedを想定しています。
#include <ctype.h>
#include <string.h>
#include <httpd.h>
#include <http_config.h>
#include <http_protocol.h>
#include <http_log.h>
#include <apr_hash.h>
#include <apr_strings.h>

#define MAX_SIZE 1048576

static void blog_hooks(apr_pool_t *pool);
static int blog_handler(request_rec *r);

module AP_MODULE_DECLARE_DATA blog_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    blog_hooks
};

static void blog_hooks(apr_pool_t *pool)
{
    ap_hook_handler(blog_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

static apr_hash_t *parse_form_from_string(request_rec *r, char *args)
{
    apr_hash_t *form;
    apr_array_header_t *values;
    char *pair;
    char *eq;
    const char *delim = "&";
    char *last;
    if (args == NULL) {
        return NULL;
    }

    form = apr_hash_make(r->pool);

    for (pair = apr_strtok(args, delim, &last);
         pair != NULL;
         pair = apr_strtok(NULL, delim, &last)){
        for (eq = pair; *eq; ++eq){
            if(*eq == '+'){
                *eq = ' ';
            }
        }
        eq = strchr(pair, '=');
        if (eq) {
            *eq++ = '\0';
            ap_unescape_url(pair);
            ap_unescape_url(eq);
        }
        else {
            eq = "";
            ap_unescape_url(pair);
        }
        values = apr_hash_get(form, pair, APR_HASH_KEY_STRING);
        if (values == NULL) {
            values = apr_array_make(r->pool, 1, sizeof(char*));
            apr_hash_set(form, pair, APR_HASH_KEY_STRING, values);
        }
        *((char **)apr_array_push(values)) = apr_pstrdup(r->pool, eq);
    }

    return form;
}

static int parse_form_from_POST(request_rec *r, apr_hash_t **form)
{
    int bytes, eos;
    apr_size_t count;
    apr_status_t rv;
    apr_bucket_brigade *bb;
    apr_bucket_brigade *bbin;
    char *buf;
    apr_bucket *b;
    apr_bucket *nextb;
    const char *clen = apr_table_get(r->headers_in, "Content-Length");

    if (clen != NULL){
        bytes = strtol(clen, NULL, 0);
        if (bytes >= MAX_SIZE) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                          "Request too big (%d bytes; limit %d)",
                          bytes, MAX_SIZE);
            return HTTP_REQUEST_ENTITY_TOO_LARGE;
        }
    }
    else {
        bytes = MAX_SIZE;
    }
    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    count = 0;
    eos = 0;
    do {
        rv = ap_get_brigade(r->input_filters, bbin, AP_MODE_READBYTES,
                            APR_BLOCK_READ, bytes);

        if (rv != APR_SUCCESS) {
            return HTTP_INTERNAL_SERVER_ERROR;
        }
        for (b = APR_BRIGADE_FIRST(bbin);
             b != APR_BRIGADE_SENTINEL(bbin);
             b = nextb) {
            nextb = APR_BUCKET_NEXT(b);
            if (APR_BUCKET_IS_EOS(b)) {
                eos = 1;
            }
            if (!APR_BUCKET_IS_METADATA(b)) {
                if (b->length != (apr_size_t)(-1)) {
                    count += b->length;
                    if (count > MAX_SIZE) {
                        apr_bucket_delete(b);
                    }
                }
            }
            if (count <= MAX_SIZE) {
                APR_BUCKET_REMOVE(b);
                APR_BRIGADE_INSERT_TAIL(bb, b);
            }
        }
    }while (!eos);
    if (count > MAX_SIZE) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                      "Request too big (%d bytes; limit %d)",
                      count, MAX_SIZE);
        return HTTP_REQUEST_ENTITY_TOO_LARGE;
    }
    buf = apr_palloc(r->pool, count+1);
    rv = apr_brigade_flatten(bb, buf, &count);
    if (rv != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                      "Error (flatten) reading form data");
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    buf[count] = '\0';
    *form = parse_form_from_string(r, buf);
    return OK;
}

static int blog_handler(request_rec *r)
{
    int rv = 0;
    apr_hash_t *formdata = NULL;

    if (!r->handler || (strcmp(r->handler, "blog") != 0)) {
        return DECLINED ;
    }

    if (r->method_number != M_POST) {
        return HTTP_METHOD_NOT_ALLOWED;
    }

    const char * ctype = apr_table_get(r->headers_in, "Content-Type");
    if (ctype && (strcasecmp(ctype,
                             "application/x-www-form-urlencoded") == 0)){
        rv =parse_form_from_POST(r, &formdata);
        if (rv != OK){
            return rv;
        }
    }

    ap_set_content_type(r, "text/html;charset=utf-8");
    ap_rputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">
"
             "<html><head><title>Apache Module</title></head>"
             "<body>"
             "<p>This is my Apache module!</p>",
             r);

    if (formdata == NULL) {
        ap_rputs("<p>No form data found.</p>", r);
    }
    else {
        apr_array_header_t *arr;
        char *key, *p;
        apr_ssize_t klen;
        apr_hash_index_t *index;
        char **val_ptr;

        ap_rprintf(r, "<h2>Form data supplied by method %s </h2>
<dl>",
                   r->method);

        for (index = apr_hash_first(r->pool, formdata);
             index != NULL;
             index = apr_hash_next(index)){
            apr_hash_this(index,
                          (const void **)&key,
                          &klen,
                          (void **)&arr);
            ap_rprintf(r, "<dt>%s</dt>
", ap_escape_html(r->pool, key));
            for (val_ptr = apr_array_pop(arr);
                 val_ptr != NULL;
                 val_ptr = apr_array_pop(arr)){
                char *val = *val_ptr;
                ap_rprintf(r, "<dd>%s</dd>
",
                           ap_escape_html(r->pool, val));
            }
        }
        ap_rputs("</dl>", r);
    }
    ap_rputs("</body></html>", r);
    return OK;
}


matssaku at 00:58|PermalinkComments(0)TrackBack(0)clip!

November 17, 2008

Apache Moduleをつくる - GETパラメータの処理 -

このエントリの内容、およびサンプルコードの多くの部分で、Apache Modules Book(リンクはAmazonアソシエイト)を引用しています。

Apacheモジュールで、GETパラメータを処理する方法を解説します。

GETパラメータとは、ブラウザでURLの後ろに(?○○=××&△△=□□)という形式でくっついてるやつです。

Apacheモジュールでは、request_rec構造体のメンバであるargsに一連の文字列としてGETパラメータが渡されます。

各パラメータはエスケープされた状態で格納されており、処理の手順は、
(1)argsをデリミタ'&'でトークンに分割する。
(2)トークンからkeyとvalue('='の左側と右側)を取り出す。
(3)keyとvalueをap_unescape_url関数でアンエスケープする。
という流れになります。

GETパラメータの処理は関数
apr_hash_t *parse_form_from_string(request_rec* r, char* args)
にまとめることにします。

返り値のapr_hash_tは、APR(Apache Portable Runtime)で提供されているハッシュマップの実装です。

このハッシュマップにGETのパラメータを
KEY(char *):VALUEの配列(apr_array_header_t *)
の形式で格納するものとします。

サンプルコードは以下のとおりです。
static apr_hash_t *parse_form_from_string(request_rec *r, char *args)
{
    apr_hash_t *form;
    apr_array_header_t *values;
    char *pair;
    char *eq;
    const char *delim = "&";
    char *last;
    if (args == NULL) {
        return NULL;
    }
    /* ハッシュマップの作成 */
    form = apr_hash_make(r->pool);

    /* delim('&')でトークンに分割 */
    for (pair = apr_strtok(args, delim, &last);
         pair != NULL;
         pair = apr_strtok(NULL, delim, &last)){
        for (eq = pair; *eq; ++eq){
            if(*eq == '+'){
                *eq = ' ';
            }
        }
        /* '='でkeyとvalueに分割 */
        eq = strchr(pair, '=');
        if (eq) {
            *eq++ = '\0';
            ap_unescape_url(pair);
            ap_unescape_url(eq);
        }
        else {
            eq = "";
            ap_unescape_url(pair);
        }
        /* 
           valueを配列(apr_array_header_t)として
           ハッシュマップに登録
        */
        values = apr_hash_get(form, pair, APR_HASH_KEY_STRING);
        if (values == NULL) {
            values = apr_array_make(r->pool, 1, sizeof(char*));
            apr_hash_set(form, pair, APR_HASH_KEY_STRING, values);
        }
        *((char **)apr_array_push(values)) = apr_pstrdup(r->pool, eq);
    }

    return form;
}


この関数は、以下のように呼び出します。
apr_hash_t *formdata = parse_form_from_string(r, r->args);
この関数を利用してGETパラメータを表示する、モジュール全体のサンプルコードは以下のようになります。
#include <ctype.h>
#include <string.h>

#include <httpd.h>
#include <http_config.h>
#include <http_protocol.h>
#include <http_log.h>
#include <apr_hash.h>
#include <apr_strings.h>

static void blog_hooks(apr_pool_t *pool);
static int blog_handler(request_rec *r);

module AP_MODULE_DECLARE_DATA blog_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    blog_hooks
};

static void blog_hooks(apr_pool_t *pool)
{
    ap_hook_handler(blog_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

static apr_hash_t *parse_form_from_string(request_rec *r, char *args)
{
    apr_hash_t *form;
    apr_array_header_t *values;
    char *pair;
    char *eq;
    const char *delim = "&";
    char *last;
    if (args == NULL) {
        return NULL;
    }

    form = apr_hash_make(r->pool);

    for (pair = apr_strtok(args, delim, &last);
         pair != NULL;
         pair = apr_strtok(NULL, delim, &last)){
        for (eq = pair; *eq; ++eq){
            if(*eq == '+'){
                *eq = ' ';
            }
        }
        eq = strchr(pair, '=');
        if (eq) {
            *eq++ = '\0';
            ap_unescape_url(pair);
            ap_unescape_url(eq);
        }
        else {
            eq = "";
            ap_unescape_url(pair);
        }
        values = apr_hash_get(form, pair, APR_HASH_KEY_STRING);
        if (values == NULL) {
            values = apr_array_make(r->pool, 1, sizeof(char*));
            apr_hash_set(form, pair, APR_HASH_KEY_STRING, values);
        }
        *((char **)apr_array_push(values)) = apr_pstrdup(r->pool, eq);
    }

    return form;
}

static apr_hash_t *parse_form_from_GET(request_rec *r)
{
    return parse_form_from_string(r, r->args);
}

static int blog_handler(request_rec *r)
{
    apr_hash_t *formdata = NULL;

    if (!r->handler || (strcmp(r->handler, "blog") != 0)) {
        return DECLINED ;
    }

    if (r->method_number != M_GET) {
        return HTTP_METHOD_NOT_ALLOWED;
    }
    ap_set_content_type(r, "text/html;charset=utf-8");
    ap_rputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n"
             "<html><head><title>Apache Module</title></head>"
             "<body><h1>Hello World!</h1>"
             "<p>This is my Apache module</p>",
             r);

    formdata = parse_form_from_GET(r);

    if (formdata == NULL) {
        ap_rputs("<p>No form data found.</p>", r);
    }
    else {
        apr_array_header_t *arr;
        char *key, *p;
        apr_ssize_t klen;
        apr_hash_index_t *index;
        char **val_ptr;

        ap_rprintf(r, "<h2>Form data supplied by method %s </h2>\n<dl>",
                   r->method);

        for (index = apr_hash_first(r->pool, formdata);
             index != NULL;
             index = apr_hash_next(index)){
            apr_hash_this(index,
                          (const void **)&key,
                          &klen,
                          (void **)&arr);
            ap_rprintf(r, "<dt>%s</dt>\n", ap_escape_html(r->pool, key));
            for (val_ptr = apr_array_pop(arr);
                 val_ptr != NULL;
                 val_ptr = apr_array_pop(arr)){
                char *val = *val_ptr;
                ap_rprintf(r, "<dd>%s</dd>\n",
                           ap_escape_html(r->pool, val));
            }
        }
        ap_rputs("</dl>", r);
    }
    ap_rputs("</body></html>", r);
    return OK;
}
ハッシュマップapr_hash_tや、可変長配列apr_array_header_tなど、APRで提供されているAPIの使い方については、アリエルネットワークの井上誠一郎さんによるチュートリアルが非常に参考になります。
ちなみにこのチュートリアルはApache Modules Bookにも載っていて、Apacheコアディベロッパーのお墨付きとなっています。

matssaku at 23:05|PermalinkComments(0)TrackBack(0)clip!

November 12, 2008

[自分メモ]Apache Moduleをつくる - 動かしてみる

前の記事で生成したモジュールを動かしてみます。

apxsコマンドはMakefileも生成してくれるので、ビルドとインストールは簡単です。
$ cd blog
$ make
$ sudo make install
Apacheのインストールディレクトリ/modulesディレクトリにmod_blog.soがインストールされると思います。

インストールしたモジュールを動作させるためには、Apacheの設定を追加する必要があります。Apacheのインストールディレクトリ/conf/httpd.conf、もしくはそこからincludeされるファイルに、以下の記述を追加します。
LoadModule blog_module modules/mod_blog.so
<Location /blog>
SetHandler blog
</Location>
上の設定は、URLパス/blog以下のリクエストに対して"blog"という文字列をhandlerとしてセットする、という意味になります。この設定により、前の記事で参照した、
static int blog_handler(request_rec *r)
{
    if (strcmp(r->handler, "blog")) {
        return DECLINED;
    }
    r->content_type = "text/html";

    if (!r->header_only)
        ap_rputs("The sample page from mod_blog.c\n", r);
    return OK;
}
strcmp(r->handler, "blog")の判定で、DECLINEされることなく処理が行われるようになります。

設定が終わったらApacheの起動を行います。起動もMakefileタスクとして実行できます。
$ sudo make start

http://ホスト名/blog/にアクセスすることで、
The sample page from mod_blog.c
という文字列が表示されることが確認できると思います。

Apacheの停止もMakefileのタスクとして実行できます。
$ sudo make stop


matssaku at 23:32|PermalinkComments(0)TrackBack(0)clip!

[自分メモ]Apache Moduleをつくる - モジュール宣言とフック関数

Apacheのモジュール開発とはフック関数を開発することである、といって良いと思います。

Apacheが起動した後、HTTPリクエストを受けてからレスポンスを返すまでにはいくつかの段階があります。

開発者は、その中の任意のポイントに独自のフック関数を登録することにより、リクエストの転送などの処理を行ったり、レスポンスを関数内で動的に生成する処理を行ったりすることができます。

それを踏まえた上で、前回の記事の続きです。
前回の記事で示したコマンドを実行すると、blog/mod_blog.cというファイルが生成されます。

これがモジュールのソースコードの雛形になります。

ソースコードの内、始めに注目するべきなのは末尾のモジュール宣言の部分です。
/* Dispatch list for API hooks */
module AP_MODULE_DECLARE_DATA blog_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       */
    blog_register_hooks  /* register hooks                      */
};
NULLがたくさん入っているのは、本来はコンフィグ関連の関数を登録する場所です。コンフィグを利用しない場合はNULLを設定することができます。

blog_register_hooksはフック関数を登録するための関数です。この中でフック関数の登録を行います。
blog_register_hooksの中身は以下のようになっています。
static void blog_register_hooks(apr_pool_t *p)
{
    ap_hook_handler(blog_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
ap_hook_handlerによりフック関数の登録処理を行っています。

ap_hook_handlerはHTTPリクエストに対する応答内容を生成するためのフック関数を登録するために利用される関数です。

フック関数の登録用の関数としては他にも
ap_hook_quick_handler
リクエストの処理が始まる前に呼ばれるフック関数の登録
ap_hook_post_config
サーバの起動後、コンフィグ終了後に呼ばれるフック関数の登録
などが存在します。

参考:http://httpd.apache.org/docs/2.2/ja/developer/modules.html

mod_blog.cではap_hook_handlerでフック関数としてblog_handlerが登録されておりこの中でレスポンスの生成が行われます。
static int blog_handler(request_rec *r)
{
    if (strcmp(r->handler, "blog")) {
        return DECLINED;
    }
    r->content_type = "text/html";

    if (!r->header_only)
        ap_rputs("The sample page from mod_blog.c\n", r);
    return OK;
}
3〜5行目では、リクエストに含まれる"handler"という要素をチェックしています。これは、このリクエストが、このフック関数で処理すべきリクエストかどうかをチェックする処理です。この処理を行わないと、全てのリクエストに対してこの関数内の処理が実行されてしまいます。

6行目はレスポンスヘッダのcontent-typeを設定しています。8,9行目はレスポンス内容の書き込みを行っています。!r->header_onlyはリクエストがHEADだった場合は、レスポンスを行わないことを意味します。

matssaku at 01:14|PermalinkComments(0)TrackBack(0)clip!

[自分メモ]Apache Moduleをつくる - はじめの一歩

Apache Moduleの開発にあたっては、apxsというツールを利用することができます。

Apacheをソースコードからインストールした場合は、インストールディレクトリのbinの下にあります。

rpmやdebでインストールする場合、開発用パッケージ(httpd-devel.rpm、apache2-dev.deb)が必要です。

apxsコマンドを利用することで、モジュールの雛形コードを生成できます。モジュール名を「blog」(ライブラリ名はmod_blog)とする場合の実行例は以下のとおりです。
$ apxs -g -n blog
カレントディレクトリにblogというディレクトリが作成され、その下にソースコードファイルの雛形とMakefileが配置されます。



matssaku at 00:51|PermalinkComments(0)TrackBack(0)clip!

November 10, 2008

[自分メモ]全部入りApacheの作り方

Apache HTTP Serverで、全てのモジュールを有効にしたい。

configureスクリプトのhelpを見ると--enable-mods-sharedというオプションがあり、共有ライブラリとしてビルド/インストールするモジュールを直接指定できるようです。

--enable-mods-sharedオプションには、個別のモジュール名の他に"all"というキーワードも指定できるのですが、--enable-mods-shared=allを指定しても全てのモジュールがコンパイル/インストールされる訳ではありません。

どうやら、sslなど、外部のライブラリに依存しているモジュールは外されているようです。

全てのモジュールを共有ライブラリとしてビルド/インストールするには、configureに以下のオプションを指定します。
./configure \
--with-ldap \
--enable-mods-shared="all ssl ldap cache proxy authn_alias mem_cache file_cache authnz_ldap charset_lite dav_lock disk_cache"
ただし、全部入りでビルドするためには、sslや、ldapなどのライブラリ、ヘッダファイルがあらかじめインストールされている必要があります。インストール場所によっては、--with-sslといったオプションでパスを指定する必要があります。

参考:configure - Configure the source tree


matssaku at 23:15|PermalinkComments(0)TrackBack(0)clip!