bonar note

京都のエンジニア bonar の技術的なことや技術的でない日常のブログです。

mod_clearsilver - ClearSilverのテンプレートルールを強制的に


HTMLファイルへのリクエストにclearsilverのテンプレート処理を通して返すApacheモジュールを書いてみました。


何ができるかといいますと、

  • ClearSilverのテンプレートルールが使用できる(cs include や cs var 等)
  • GETの引数をテンプレート中に引き込む(使用する)ことができる

というものです。


勉強不足と能力不足でひどいプログラムかもですが、、ソースは以下のような感じになりますた。

mod_clearsilver.c

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"

#include "ap_config.h"
#include "apr_tables.h"
#include "apr_strings.h"

#include 
#include 
#include "ClearSilver.h"

#define PARAM_DELIM ","
#define DEFAULT_TABLE_SIZE 3

module AP_MODULE_DECLARE_DATA clearsilver_module;

typedef struct {
    char* preset_params;
} mod_cs_config;

static void* init_server_config(apr_pool_t* p, server_rec* s) {
    mod_cs_config* server_config = apr_palloc(p, sizeof(mod_cs_config));
    return (void*)server_config;
}

static const char* set_server_config_presetparams(
    cmd_parms* params, void* mconfig, const char* arg) {
    mod_cs_config* server_config 
        = ap_get_module_config(params->server->module_config, &clearsilver_module);
    server_config->preset_params = apr_pstrdup(params->pool, arg);

    return NULL;
}

static const command_rec cmds[] = {
    AP_INIT_TAKE1("CS_PresetParams", set_server_config_presetparams
        , NULL, OR_ALL, "params to preset"),
    {NULL}
};

apr_table_t* get_url_params(request_rec* r) {
    if (NULL == r->args)
        return NULL;

    apr_table_t* get_params = apr_table_make(r->pool, DEFAULT_TABLE_SIZE);
    char* url_arg = apr_pstrdup(r->pool, r->args);
    char* state;
    char* hash_unit = apr_strtok(url_arg, "&", &state);

    do {
        char *key, *h_state;
        int hash_size = strlen(hash_unit);
        if (NULL != (key = apr_strtok(hash_unit, "=", &h_state))
            && strlen(key) + 1 < hash_size) {
            char* val = key;
            val += strlen(key) + 1;

            ap_unescape_url(key);
            ap_unescape_url(val);
            apr_table_set(get_params, key, val);
        }
    } while (NULL != (hash_unit = apr_strtok(NULL, "&", &state)));

    return get_params;
}

apr_table_t* grep_specified_params(request_rec* r, apr_table_t* url_param) {
    mod_cs_config* config = ap_get_module_config(
        r->server->module_config, &clearsilver_module);
    apr_table_t* params = apr_table_make(r->pool, DEFAULT_TABLE_SIZE);

    char *token, *state;
    char* pval = apr_pstrdup(r->pool, config->preset_params);
    token = apr_strtok(pval, PARAM_DELIM, &state);
    do {
        const char* url_var = apr_table_get(url_param, token);
        if (NULL != url_var) {
            apr_table_set(params, token, url_var);
        }
    } while (NULL != (token = apr_strtok(NULL, PARAM_DELIM, &state)));

    return params;
}

static int callback_set_hdf_var(void* hdf, const char* key, const char* val) {
    if (NULL != key) {
        hdf_set_value(hdf, key, val);
        return 1;
    }
    return 0;
}

NEOERR* cs_filter(void *data, char *s) {  
    request_rec* r = (request_rec*)data;
    ap_rputs(s, r);
    return STATUS_OK;
}

static int clearsilver_handler(request_rec *r) {
    if (strcmp(r->handler, "clearsilver")) {
        return DECLINED;
    }

    r->content_type = "text/html";
    if (r->header_only) {
        return OK;
    }

    if (0 != access(r->filename, R_OK)) {
        return HTTP_NOT_FOUND;
    }

    HDF *hdf;
    hdf_init(&hdf);

    // make URL param table, and grep it with CS_PresetParams 
    apr_table_t *url_params, *conf_params;
    url_params = get_url_params(r);
    if (NULL != url_params) {
        conf_params = grep_specified_params(r, url_params);
        if (!apr_is_empty_table(conf_params)) {
             apr_table_do(callback_set_hdf_var, hdf, conf_params, NULL, NULL);
        }
    }

    CSPARSE *cs;
    cs_init(&cs, hdf);

    cs_parse_file(cs, r->filename);
    cs_render(cs, r, cs_filter);

    cs_destroy(&cs);
    hdf_destroy(&hdf);

    return OK;
}

static void clearsilver_register_hooks(apr_pool_t *p) {
    ap_hook_handler(clearsilver_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA clearsilver_module = {
    STANDARD20_MODULE_STUFF, 
    NULL,                  /* create per-dir    config structures */
    NULL,                  /* merge  per-dir    config structures */
    init_server_config,    /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    cmds,                  /* table of config file commands       */
    clearsilver_register_hooks  /* register hooks                      */
};

apxsで作成したMakefileを以下のような感じで編集して、ClearSilver関連のライブラリをリンクしして、make; make install でOKです。

INCLUDES=-I/usr/local/include/ClearSilver
LIBS=-lneo_cs -lneo_utl -lneo_cgi -lz

apacheのconfには以下のような感じで設定します。

LoadModule clearsilver_module modules/mod_clearsilver.so

    SetHandler clearsilver
    CS_PresetParams id,var1,var2

CS_PresetParamsという項目は、直接ClearSilverとは関係ないのですが(--;;)、foo.html?var=~~みたいに指定された際に、どの引数を受け取るか(テンプレート中で使用するか)というオプションです。複数ある場合はカンマ区切りで入力します。ここに書いていない引数が指定された場合には無視します。cs include だけだとありがたみがないので付けてみました。


実際にどんな感じになるかといいますと、こんな感じになります。


デモページ:
http://scalar.jp/sandbox/cs/sample.html?var1=search


リンクをパチパチ切り替えると、HTMLの内容も変わります。これはClearSilverを使って1つのテンプレートのみで表示しています。上記URLのファイル(sample.html)は実際にはこんなファイルになってます。
#これぐらいだったら普通はJavaScriptですが。。

<html>
<body>
<?cs set:midashi="clearsilver - test page" ?>
<h1><?cs var:midashi ?></h1> [ 
<?cs if:var1=="search" ?><b>人力検索</b><?cs else ?>
<a href="sample.html?var1=search">人力検索</a><?cs /if ?> | 
<?cs if:var1=="antena" ?><b>アンテナ</b><?cs else ?>
<a href="sample.html?var1=antena">アンテナ</a><?cs /if ?> | 
<?cs if:var1=="bookmark" ?><b>ブックマーク</b><?cs else ?>
<a href="sample.html?var1=bookmark">ブックマーク</a><?cs /if ?> | 
<?cs if:var1=="diary" ?><b>ダイアリー</b><?cs else ?>
<a href="sample.html?var1=diary">ダイアリー</a><?cs /if ?> ]
<hr size=1 />
<?cs if:var1=="search" ?>
search body
<?cs /if ?>
<?cs if:var1=="antena" ?>
antena body
<?cs /if ?>
<?cs if:var1=="bookmark" ?>
bookmark body
<?cs /if ?>
<?cs if:var1=="diary" ?>
diary body
<?cs /if ?>
</body> 
</html>

例によってサンプルファイルがよくないかもですが、HTMLの管理という面でも共通ファイルを切り出してincludeしたりすれば素敵ですし、URL引数を使ってある程度ダイナミックにコンテンツをだし分けたりできるかもです。


ここにさらに自作の関数なんかを入れられると夢が広がるのですが、ClearSilverのAPIを見た感じそれっぽいのがなく、cs_register_strfunc という関数があるのですが、これも

Note that we explicitly don't provide any associated data or anything to attempt to keep you from
using this as a generic callback...

とはっきり書いてあります。cs_filterの中で内容をもう一回パースして、特殊文法を抜き出して、、って方法もありそうですが、どうなんでしょう。うーん。。いい方法をご存知の方是非教えてください!この部分ができて、そこをプラグインにして抜き出せたら奇麗かも。


ちなみに、上記のテンプレート処理を行う事で、普通にHTMLを返すのに比べてどれくらい遅くなるのか ab してみました(ab -c 10 -n 100)。計測する前にそれぞれ apache を restart しました。


ちなみにwebサーバのスペックはこんな感じです。

cpuinfo:
Intel(R) Pentium(R) 4 CPU 2.80GHz

maminfo:
MemTotal:       253728 kB
MemFree:         28404 kB
Buffers:         36592 kB
Cached:         144888 kB

Apache/2.2.0


結果はこんな感じ。

field static mod_clearsilver
HTML transferred(bytes) 29400 29400
Requests per second[#/sec] (mean) 198.99 147.88
Time per request[ms] (mean) 50.254 67.622


うう、、、、


ClearSilverは悪くないんです!僕が悪いんです!出直してきます!


ほんとすいません。