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

トラックバックURL

この記事にコメントする

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