2009/03/11


readline使ってコマンドライン提供、curlで通信、結果をjson-cでパースまで作った。燃え尽きた。
追記
shebangから使えるようにした。
#!/usr/bin/dansh
以下コード // for MSVC: cl -I.. /Tp dansh.cpp curl.lib readline.lib ..\Release\json.lib
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
namespace json {
#include "json.h"
}
#define READLINE_STATIC
#include <readline/readline.h>
#include <curl/curl.h>
#define API_URL "http://api.dan.co.jp/perleval.cgi?c=callback&s="

typedef struct {
    char* data;     // response data from server
    size_t size;    // response size of data
} MEMFILE;

MEMFILE*
memfopen() {
    MEMFILE* mf = (MEMFILE*) malloc(sizeof(MEMFILE));
    mf->data = NULL;
    mf->size = 0;
    return mf;
}

void
memfclose(MEMFILE* mf) {
    if (mf->data) free(mf->data);
    free(mf);
}

size_t
memfwrite(char* ptr, size_t size, size_t nmemb, void* stream) {
    MEMFILE* mf = (MEMFILE*) stream;
    int block = size * nmemb;
    if (!mf->data)
        mf->data = (char*) malloc(block);
    else
        mf->data = (char*) realloc(mf->data, mf->size + block);
    if (mf->data) {
        memcpy(mf->data + mf->size, ptr, block);
        mf->size += block;
    }
    return block;
}

char*
memfstrdup(MEMFILE* mf) {
    char* buf = (char*) malloc(mf->size + 1);
    memcpy(buf, mf->data, mf->size);
    buf[mf->size] = 0;
    return buf;
}

char*
url_encode_alloc(const char* str, int force_encode) {
    const char* hex = "0123456789abcdef";

    char* buf = NULL;
    unsigned char* pbuf = NULL;
    int len = 0;

    if (!str) return NULL;
    len = strlen(str)*3;
    buf = (char*) malloc(len+1);
    memset(buf, 0, len+1);
    pbuf = (unsigned char*)buf;
    while(*str) {
        unsigned char c = (unsigned char)*str;
        if (c == ' ')
            *pbuf++ = '+';
        else if (c & 0x80 || force_encode) {
            *pbuf++ = '%';
            *pbuf++ = hex[c >> 4];
            *pbuf++ = hex[c & 0x0f];
        } else
            *pbuf++ = c;
        str++;
    }
    return buf;
}

void do_dan(const char* line) {
    char* source = url_encode_alloc(line, TRUE);
    char* url = (char*) malloc(strlen(API_URL) + strlen(source) + 1);
    strcpy(url, API_URL);
    strcat(url, source);

    MEMFILE* mf = memfopen();
    CURL* curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf);
    CURLcode res = curl_easy_perform(curl);
    free(url);
    if (res == CURLE_OK) {
        char* data = memfstrdup(mf);
        memfclose(mf);

        // remove callback function
        char* ptr;
        ptr = strrchr(data, ')');
        if (ptr) *ptr = 0;
        ptr = strchr(data, '(');
        if (ptr) *ptr++ = 0;
        else ptr = data;

        json::json_object *obj = json::json_tokener_parse(ptr);
        if (!is_error(obj)) {
            json::json_object *result = json::json_object_object_get(obj, "result");
            if (!is_error(result)) {
                printf("%s\n", json::json_object_to_json_string(result));
                json_object_put(result);
            }
        }
        free(data);
    }
    curl_easy_cleanup(curl);
}

int main(int argc, char **argv)
{
    char* line = NULL;

    //json::mc_set_debug(1);
    if (argc == 2) {
        FILE* fp = fopen(argv[1], "rb");
        if (!fp) {
            perror("can't open file");
            exit(-1);
        }
        char buf[BUFSIZ];
        while (fgets(buf, sizeof(buf), fp)) {
            if (!line) {
                if (strncmp(buf, "#!", 2))
                    line = strdup(buf);
            } else {
                line = (char*) realloc(line, strlen(line) + strlen(buf) + 1);
                strcat(line, buf);
            }
        }
        fclose(fp);
        do_dan(line);
        free(line);
    }
    if (!isatty(fileno(stdin))) {
        char buf[BUFSIZ];
        while (fgets(buf, sizeof(buf), stdin)) {
            if (!line) {
                if (strncmp(buf, "#!", 2))
                    line = strdup(buf);
            } else {
                line = (char*) realloc(line, strlen(line) + strlen(buf) + 1);
                strcat(line, buf);
            }
        }
        do_dan(line);
        free(line);
    } else {
        while (line = readline("dan> ")) {
            do_dan(line);
            free(line);
        }
    }
    return 0;
}

参考文献: 404 Blog Not Found:Ajax - perlを実行するAPI
Posted at by



2009/02/24


memcachedサーバへのアクセスモジュールは数ありますが、一般的にはlibmemcachedが使われる事が多いと思います。
Perlにおいても
  • Cache::Memcached
  • Cache::Memcached::Fast
  • Cache::Memcached::libmemcached
  • Memcached::libmemcached
と数種類存在し、一般的に使用されるlibmemcachedのラッパインタフェースであるCache::Memcached::libmemcachedが使われる事が多い様に思います。
libmemcachedに関しては、以前Windowsへのポーティングを行い、オフィシャルへのパッチ送付も行いました。
成果物としてはcodereposに置いてあります。オフィシャルからもリンクを張って頂けるようになりました。
さらにCache::Memcached::Fastについても、以前Windowsへのポーティングを行い、Cache::Memcached::Fast version 0.13に取り込まれました。
つまり特殊な事をせずにWindowsから利用出来る高速なPerlのmemcachedクライアントライブラリとしてはCache::Memcached::Fastになります。
ちなみこのCache::Memcached::Fast、実はlibmemcachedと比較しても格段に速く、tokuhiromさんが取ったベンチマークでも素晴らしい結果を叩き出してくれています。

さて今日は、このCache::Memcached::Fastが内部で使用しているXSクライアントモジュールを使用して、高速にmemcachedにアクセスする物を作ってみたいと思います。
このCache::Memcached::FastのXSコードは、Perlに依存した部分とPerlに依存していない部分で分けられており、その後者は一般的なC言語のソースから呼び出しが可能になっています。
なぜこのCache::Memcached::Fastが速いかと言うと、libmemcachedの様に逐次送信を行っているのではなくwritev(2)を使った一括送信を行っているからです。またCache::Memcached::Fastはselect(2)ではなくpoll(2)を使っている為、FD_SETの設定を毎回行わなくて良いというのも微量ではありますが影響しているのではないかと思っています。
このクライアントモジュールは、libmemcachedの様に単一取得(memcached_get)や複数取得(memcached_mget)のAPI呼び出し時に逐次送受信されるのではなく、client_prepare_get/client_prepare_setを使った前準備方式を使っています。これにより複数の問い合わせに対しても内部では一括送信してくれ、一括で受信してくれます。libmemcachedは複数の問い合わせに対してそれぞれ結果待ちをし、全ての問い合わせが完了した時点で制御が戻ります。これについてはMEMCACHED_BEHAVIOR_NO_BLOCKを使用する事でよく似た動作をする事が出来ます。
このクライアントモジュールを使用した実際のサンプルコードは以下の様になります。
/* fast memcached client using client module of Cache::Memcached::Client.
 *   compile   : gcc -o a.exe foo.c libclient.a
 *   for win32 : gcc -o a.exe foo.c libclient.a -lws2_32
 */

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "client.h"

static void* alloc_value(value_size_type value_size, void **opaque) {
    *opaque = (char *) malloc(value_size + 1);
    memset(*opaque, 0, value_size + 1);
    return (void *) *opaque;
}

static void free_value(void *opaque) {
    free(opaque);
}

static void result_store(void *arg, void *opaque, int key_index, void *meta) {
    char* res = (char*) opaque;
    if (res) *(char**)arg = strdup(res);
    else     *(char**)arg = strdup("");
}

int main(int argc, char* argv[]) {
    struct client* c = NULL;
    const char* host = "127.0.0.1";
    const char* port = "11211";
    const char* key = "foo";
    const char* value = "bar";
    struct result_object object;
    char* result = NULL;
    int ret;

    /* initialize client module of Cache::Memcached::Fast */
    c = client_init();

    /* add memcached server */
    client_add_server(c, host, strlen(host), port, strlen(port), 1.0, 1);

    /*--- set function --------------------------------*/
    /* initialize staff context for set */
    object.alloc = NULL;
    object.store = result_store;
    object.free = NULL;
    object.arg = NULL;
    client_reset(c, &object, 1);

    /* prepare for set function */
    printf("setting value of '%s' as '%s'\n", key, value);
    client_prepare_set(c, CMD_SET, 0, key, strlen(key), 0, 0, value, strlen(value));

    /* execute set function */
    client_execute(c);
    /*-------------------------------------------------*/

    /*--- get function --------------------------------*/
    /* initialize staff context for get */
    object.alloc = alloc_value;
    object.store = result_store;
    object.free = free_value;
    object.arg = &result;
    client_reset(c, &object, 0);

    /* prepare for get function */
    printf("getting value of '%s'\n", key);
    client_prepare_get(c, CMD_GET, 0, key, strlen(key));

    /* execute set function */
    client_execute(c);
    /*-------------------------------------------------*/

    printf("result value of '%s' is '%s'\n", key, result);

    free(result);

    return 0;
}
少し変わったコードになりますが、メモリの確保から開放まで自分でハンドリングでき、自前の構造を使った処理も行えるかと思います。
なお、libmemcachedとCache::Memcached::Fastのクライアントモジュールでget/setを繰り返すベンチマークを取ってみました。
まずはlibmemcachedのコード
#include <winsock2.h>
#include <memcached.h>
#include <stdio.h>

#define SERVER_NAME "127.0.0.1"
#define SERVER_PORT 11211
#define KEY "foo"
#define VALUE "bar"

int main(void) {
    memcached_return rc;
    memcached_st *memc;
    char* value;
    int value_length = 0;
    int flags = 0;
    int n;

    memc = memcached_create(NULL);
    memcached_server_add(memc, SERVER_NAME, SERVER_PORT);
    for (n = 0; n < 30000; n++) {
        memcached_set(memc, KEY, strlen(KEY), VALUE, strlen(VALUE), 0, 0);
        value = memcached_get(memc, KEY, strlen(KEY), &value_length, &flags, &rc);
    }
    memcached_free(memc);
    return 0;
}
次にCache::Memcached::Fast(CMF)のクライアントモジュールのコード
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "client.h"

#define SERVER_NAME "127.0.0.1"
#define SERVER_PORT "11211"
#define KEY "foo"
#define VALUE "bar"

static void* alloc_value(value_size_type value_size, void **opaque) {
    *opaque = (char *) malloc(value_size + 1);
    memset(*opaque, 0, value_size + 1);
    return (void *) *opaque;
}

static void free_value(void *opaque) {
    free(opaque);
}

static void result_store(void *arg, void *opaque, int key_index, void *meta) {
    char* res = (char*) opaque;
    if (res) *(char**)arg = strdup(res);
    else     *(char**)arg = strdup("");
}

int main(int argc, char* argv[]) {
    struct client* c = NULL;
    struct result_object object_set, object_get;
    char* result = NULL;
    int n;

    c = client_init();
    client_add_server(c, SERVER_NAME, strlen(SERVER_NAME), SERVER_PORT, strlen(SERVER_PORT), 1.0, 1);

    object_set.alloc = NULL;
    object_set.store = result_store;
    object_set.free = NULL;
    object_set.arg = NULL;
    object_get.alloc = alloc_value;
    object_get.store = result_store;
    object_get.free = free_value;
    object_get.arg = &result;

    for (n = 0; n < 30000; n++) {
        client_reset(c, &object_set, 1);
        client_prepare_set(c, CMD_SET, 0, KEY, strlen(KEY), 0, 0, VALUE, strlen(VALUE));
        client_execute(c);
        client_reset(c, &object_get, 0);
        client_prepare_get(c, CMD_GET, 0, KEY, strlen(KEY));
        client_execute(c);
    }

    free(result);

    return 0;
}
計測結果は

libmemcached: 6.953125

CMF: 5.984375

となりました。get/setのループだけなのにCMFは速いですね。
Perlに依存していないので、通常アプリケーションでも問題なく使えるかと思います。
memcachedと通信するC言語で作ったプログラムのパフォーマンスが悪いと思われたならば、一度試して見られてはどうでしょうか?
Posted at by



2009/02/09


livedoor 製品で mod_access_token というのが出たみたいです。
livedoor ラボ「EDGE」 開発日誌 : 「mod_access_token」の配布開始と「EDGE src」公開のお知らせ - livedoor Blog(ブログ)

ウェブサイト上の画像やファイルに有効期限を指定して、ユーザーに一時的なダウンロードを許可する、ライブドアで独自開発したApacheモジュールです。このモジュールをApache Webサーバに組み込むことにより、画像やファイルをウェブ上で公開するときに有効期限をつけることができるようになり、Webアプリケーションと組み合わせる事で公開範囲の制御を行なう事が可能になります。

http://blog.livedoor.jp/edge_labs/archives/717201.html
modaccesstoken - Google Code

mod_access_token provides access token based secure downloading.

http://code.google.com/p/modaccesstoken/
ソースコード見たら依存が浅かったのでWindowsでビルドしてみた。
Makefile.w32
APACHE_ROOT=C:\Program Files\Apache Software Foundation\Apache2.2
CFLAGS=/I"$(APACHE_ROOT)\include" /DWIN32 /nologo
LDFLAGS=/LIBPATH:"$(APACHE_ROOT)\lib"
LIBS= libapr-1.lib libaprutil-1.lib libhttpd.lib

all : mod_access_token.so

mod_access_token.so : mod_access_token.obj
    link /nologo /DLL /OUT:$@ /EXPORT:access_token_module mod_access_token.obj $(LDFLAGS) $(LIBS)

mod_access_token.obj : mod_access_token.c
    cl -c $(CFLAGS) mod_access_token.c
なぜかmingw32では実行時にエラーが出たのであきらめました。VC6では可変長マクロが使えないので最終的にはVC8でしか試せませんでした。

ビルドしたモジュールを C:\Program Files\Apache Software Foundation\Apache2.2\modules\ に置き、httpd.confへLoadModuleを追加。対象のフォルダに以下の様に設定(.htaccess)します。
.htaccess
AccessTokenCheck On
AccessTokenAccessKey foo
AccessTokenSecret bar
このAccessTokenAccessKey(foo)が公開鍵、AccessTokenSecret(bar)が秘密鍵になります。
認証はREADMEに書かれている通り
download.pl
use strict;
use URI;
use Digest::HMAC_SHA1;

my $access_key = 'foo';
my $secret = shift || die('specify secret key!');
my $exp = time + 300; # 5minutes
my $url = 'http://localhost:8080/access_token/example.jpg';
my $uri = URI->new( $url );
my $plain = sprintf '%s%s%s%s', 'GET', $uri->path, $exp, $access_key;
my $hmac = Digest::HMAC_SHA1->new( $secret );
$hmac->add( $plain );
my $sig = $hmac->b64digest;
$uri->query_form({
    Signature => $sig,
    AccessKey => $access_key,
    Expires => $exp,
});
printf "%s\n", $uri->as_string;
といった感じ。キーが間違ってるとDECLINED(Forbidden)になります。
内部はSHA1による認証処理になってます。

誰ですか!「WindowsなんてマイナーなOSのことは知りません」とか言ってるの!(謎)

追記
パッチを当てないと動かなかったのを忘れてました。
Index: mod_access_token.c
===================================================================
--- mod_access_token.c  (revision 3)
+++ mod_access_token.c  (working copy)
@@ -2,10 +2,12 @@
 #include "httpd.h"
 #include "http_config.h"
 #include "http_protocol.h"
+#include "http_request.h"
 #include "http_log.h"
 #include "ap_config.h"
 #include "apr_sha1.h"
 #include "apr_strings.h"
+#include "apr_base64.h"
 #include "apr_lib.h"
 
 #define ACCESS_KEY_NAME "AccessKey"
Posted at by