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│Comments(0)TrackBack(0)clip!httpd | C

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔