php_threading

なんか完全に冗談のつもりで作った threading 拡張モジュールを、Alec さんという方がつい本気にして Windows にポートしたそうです (しかもネイティブスレッド使って)。

http://news.php.net/php.internals/49486

参照カウントをアトミックに増減させるとかそういう点を考慮してないから、アレだと思うんだけどなあ。

sotarokを勝手に祝う会

sotarokを勝手に祝う会に行ってまいりました!

僭越ながらこのたびはLTをさせていただいたので、その内容の紹介をさせていただきたく。(日立風)

皆様ご存知のことと思いますが、OSSコミュニティーにおいて、開発者が結婚する際には、新郎と新婦に

オリジナル○○○○○○○○○○○

を贈呈するという慣習があります。

はい、もちろん

オリジナルApacheモジュール

ですね。

というわけで、今回は

  • mod_sotarok
  • mod_mikko

を作らせていただきました。

mod_sotarok-mod_mikko at github.com

mod_sotarok.c:

/* 
 * Copyright (c) 2010 Moriyoshi Koizumi
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_main.h"
#include "http_protocol.h"
#include "http_request.h"
#include "util_script.h"
#include "http_connection.h"

#include "apr_strings.h"

#include <stdio.h>

module AP_MODULE_DECLARE_DATA sotarok_module;

static const char *data[] = {
    "VAAAAAgAAAABAAEAAAADAAEAAQAAAAIAAQADAAAAAgABAAQAAAACAAEABAAAAAIAAQABAAAAAwABAAEAAAAH",
    "AAEAAQAAAAMAAQABAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAMAAAACAAEAAQAAAAMAAQABAAAA",
    "AgABAAQAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAA",
    "AAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAABwABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEA",
    "AAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAcAAQAB",
    "AAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEA",
    "AQAAAAIAAQABAAAAAQABAAEAAAAIAAEAAQAAAAEAAQABAAAAAQABAAEAAAABAAEAAQAAAAUAAQABAAAAAwAB",
    "AAEAAAABAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAgAAAAIAAQABAAAAAQABAAEAAAAHAAEAAQAAAAMA",
    "AQAFAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAEAAAABAAEAAQAAAAgAAQABAAAAAQABAAEAAAAB",
    "AAEAAQAAAAEAAQAEAAAAAgABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAA",
    "AQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAMAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAA",
    "AAMAAQABAAAAAQABAAEAAAAFAAEAAQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEA",
    "AAAFAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAACAAEAAgAAAAEAAQAB",
    "AAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAAFAAEA",
    "AQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEAAAAFAAEAAQAAAAMAAQABAAAAAQAB",
    "AAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAAHAAEAAQAAAAMA",
    "AQABAAAAAQABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEAAAAHAAEAAQAAAAoAAQABAAAAAQABAAEAAAAC",
    "AAEABQAAAAEAAQAEAAAAAgABAAQAAAADAAEAAwAAAAIAAQABAAAAAwABAAEAAAACAAEABAAAAAMAAQABAAAA",
    "AwAAAFQAAAAAAA=="
};

static int sotarok_handler(request_rec *r)
{
    int i;
    if (strcmp(r->handler, "sotarok-handler")) {
        return DECLINED;
    }

    ap_set_content_type(r, "text/plain; charset=US-ASCII");
    if (r->header_only) return OK;
    for (i = 0; i < sizeof(data) / sizeof(data[0]); i++) {
        ap_rputs(data[i], r);
        ap_rputs("\n", r);
    }
    return OK;
}

static void sotarok_register_hooks(apr_pool_t *p)
{
    ap_hook_handler(sotarok_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA sotarok_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,                   /* per-directory config creator */
    NULL,                   /* dir config merger */
    NULL,                   /* server config creator */
    NULL,                   /* server config merger */
    NULL,                   /* command table */
    sotarok_register_hooks    /* set up other request processing hooks */
};

mod_mikko.c

/* 
 * Copyright (c) 2010 Moriyoshi Koizumi
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include <assert.h>
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_main.h"
#include "http_protocol.h"
#include "http_request.h"
#include "util_script.h"
#include "http_connection.h"

#include "apr_strings.h"

#include <stdio.h>

module AP_MODULE_DECLARE_DATA mikko_module;

static unsigned int read_u32le(const char *p)
{
    return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
}

static unsigned int read_u16le(const char *p)
{
    return p[0] | (p[1] << 8);
}

static char *base64_decode_all(apr_pool_t *pool, const char *buf, apr_size_t buf_len, apr_size_t *result_len)
{
    const char *p, *e = buf + buf_len;
    char *result, *q;
    q = result = apr_palloc(pool, (buf_len / 3) * 4);
    p = buf;
    while (p < e) {
        int l = apr_base64_decode(q, p);
        if (l == 0)
            p++;
        else {
            assert(l > 0);
            q += l;
            p += ((l + 2) / 3) * 4;
        }
    }

    *result_len = q - result;
    return result;
}

static apr_status_t mikko_do_out_filter(ap_filter_t *f, apr_bucket_brigade *bb)
{
    static const char prologue[] = "<html>\n\
<head>\n\
  <title>Happy Wedding!</title>\n\
  <style type=\"text/css\">\n\
.colored { color: red; }\n\
.uncolored { color: lightgray; }\n\
</style>\n\
</head>\n\
<body>\n\
<pre>";
    static const char epilogue[] = "</pre>\n\
</body>\n\
</html>\n";
    static const char span_colored_open[] = "<span class=\"colored\">";
    static const char span_uncolored_open[] = "<span class=\"uncolored\">";
    static const char span_close[] = "</span>";
    apr_status_t err = APR_SUCCESS;
    char *buf = 0;
    apr_size_t buf_len = 0;
    char *decoded_data = 0;
    unsigned int width, height;
    const char *p, *pe;
    const char *q;

    err = apr_brigade_pflatten(bb, &buf, &buf_len, f->r->pool);
    if (err)
        return err;
    apr_brigade_cleanup(bb);

    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create(
        prologue, sizeof(prologue) - 1, f->c->bucket_alloc));

    {
        apr_size_t decoded_len;
        p = decoded_data = base64_decode_all(f->r->pool, buf, buf_len, &decoded_len);
        pe = decoded_data + decoded_len;
    }

    if (p >= pe)
        goto error;
    width = read_u32le(p), p += 4;
    if (p >= pe)
        goto error;
    height = read_u32le(p), p += 4;
   
    q = buf; 
    for (;;) { 
        int v, c;
        if (p >= pe)
            goto error;
        v = read_u16le(p), p += 2;
        if (p >= pe)
            goto error;
        c = read_u16le(p), p += 2;
        if (!c)
            break;
        if (v) {
            APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create(
                span_colored_open, sizeof(span_colored_open) - 1, f->c->bucket_alloc));
            APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_transient_create(q, c,
                f->c->bucket_alloc));
            APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create(
                span_close, sizeof(span_close) - 1, f->c->bucket_alloc));
        } else {
            APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create(
                span_uncolored_open, sizeof(span_uncolored_open) - 1, f->c->bucket_alloc));
            APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_transient_create(q, c,
                f->c->bucket_alloc));
            APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create(
                span_close, sizeof(span_close) - 1, f->c->bucket_alloc));
        }
        q += c;
        while (*q == '\n') {
            APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_transient_create(q, 1,
                f->c->bucket_alloc));
            q++;
        }
    }

    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create(
        span_uncolored_open, sizeof(span_uncolored_open) - 1, f->c->bucket_alloc));
    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_transient_create(
        q, buf_len - (q - buf), f->c->bucket_alloc));
    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create(
        span_close, sizeof(span_close) - 1, f->c->bucket_alloc));

    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create(
        epilogue, sizeof(epilogue) - 1, f->c->bucket_alloc));

    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc));

    f->r->content_type = "text/html; charset=US-ASCII";

    return ap_pass_brigade(f->next, bb);
error:
    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_immortal_create(
        "error", sizeof("error") - 1, f->c->bucket_alloc));
    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc));
    return ap_pass_brigade(f->next, bb);
}

static void mikko_register_hooks(apr_pool_t *p)
{
    ap_register_output_filter("MIKKO_OUT", mikko_do_out_filter, NULL, AP_FTYPE_RESOURCE);
}

module AP_MODULE_DECLARE_DATA mikko_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,                   /* per-directory config creator */
    NULL,                   /* dir config merger */
    NULL,                   /* server config creator */
    NULL,                   /* server config merger */
    NULL,                   /* command table */
    mikko_register_hooks    /* set up other request processing hooks */
};

mod_sotarokを次のように組み込んで

LoadModule sotarok_module .libs/mod_sotarok.so

Listen 8080

<Location />
    SetHandler sotarok-handler
</Location>

PidFile /tmp/httpd.pid
LockFile /tmp/accept.lock
ErrorLog /tmp/error_log

Apacheにリクエストを投げると、次のような文字列が帰ってきます。

VAAAAAgAAAABAAEAAAADAAEAAQAAAAIAAQADAAAAAgABAAQAAAACAAEABAAAAAIAAQABAAAAAwABAAEAAAAH
AAEAAQAAAAMAAQABAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAMAAAACAAEAAQAAAAMAAQABAAAA
AgABAAQAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAA
AAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAABwABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEA
AAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAcAAQAB
AAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEA
AQAAAAIAAQABAAAAAQABAAEAAAAIAAEAAQAAAAEAAQABAAAAAQABAAEAAAABAAEAAQAAAAUAAQABAAAAAwAB
AAEAAAABAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAgAAAAIAAQABAAAAAQABAAEAAAAHAAEAAQAAAAMA
AQAFAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAEAAAABAAEAAQAAAAgAAQABAAAAAQABAAEAAAAB
AAEAAQAAAAEAAQAEAAAAAgABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAA
AQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAMAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAA
AAMAAQABAAAAAQABAAEAAAAFAAEAAQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEA
AAAFAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAACAAEAAgAAAAEAAQAB
AAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAAFAAEA
AQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEAAAAFAAEAAQAAAAMAAQABAAAAAQAB
AAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAAHAAEAAQAAAAMA
AQABAAAAAQABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEAAAAHAAEAAQAAAAoAAQABAAAAAQABAAEAAAAC
AAEABQAAAAEAAQAEAAAAAgABAAQAAAADAAEAAwAAAAIAAQABAAAAAwABAAEAAAACAAEABAAAAAMAAQABAAAA
AwAAAFQAAAAAAA==

見たところ base64 なわけですが、これをデコードしてみると、

$ php -r 'echo bin2hex(base64_decode(file_get_contents("/tmp/out.txt")));'
 
540000000800000001000100000003000100010000000200010003000000020001000400000002000100040000000200010001000000030001000100000007000100010000000300010001000000010001000500000001000100040000000200010004000000030001000300000002000100010000000300010001000000020001000400000003000100010000000300010001000000030001000100000001000100010000000300010001000000010001000100000003000100010000000100010001000000030001000100000001000100010000000300010001000000070001000100000003000100010000000100010001000000050001000100000003000100010000000100010001000000030001000100000003000100010000000300010001000000030001000100000001000100010000000700010001000000030001000100000003000100010000000100010001000000030001000100000001000100010000000300010001000000010001000100000003000100010000000200010001000000010001000100000008000100010000000100010001000000010001000100000001000100010000000500010001000000030001000100000001000100010000000300010001000000030001000100000003000100020000000200010001000000010001000100000007000100010000000300010005000000010001000500000001000100040000000200010004000000030001000100000001000100010000000800010001000000010001000100000001000100010000000100010004000000020001000100000003000100010000000100010001000000030001000100000003000100010000000300010001000000010001000100000001000100010000000100010001000000010001000300000003000100010000000300010001000000030001000100000001000100010000000300010001000000010001000100000005000100010000000700010001000000090001000100000001000100010000000100010001000000010001000100000005000100010000000300010001000000010001000100000003000100010000000300010001000000030001000100000002000100020000000100010001000000030001000100000003000100010000000300010001000000030001000100000001000100010000000300010001000000010001000100000005000100010000000700010001000000090001000100000001000100010000000100010001000000010001000100000005000100010000000300010001000000010001000100000003000100010000000300010001000000030001000100000003000100010000000100010001000000030001000100000007000100010000000300010001000000010001000100000003000100010000000100010001000000050001000100000007000100010000000a0001000100000001000100010000000200010005000000010001000400000002000100040000000300010003000000020001000100000003000100010000000200010004000000030001000100000003000000540000000000

のように、何か意味ありげなオクテット列が続いていることが分かります。

では、mod_mikko.so を組み込んでみましょう。

LoadModule sotarok_module .libs/mod_sotarok.so
LoadModule mikko_module .libs/mod_mikko.so

Listen 8080

<Location />
    SetHandler sotarok-handler
    SetOutputFilter MIKKO_OUT
</Location>

PidFile /tmp/httpd.pid
LockFile /tmp/accept.lock
ErrorLog /tmp/error_log

出力:

VAAAAAgAAAABAAEAAAADAAEAAQAAAAIAAQADAAAAAgABAAQAAAACAAEABAAAAAIAAQABAAAAAwABAAEAAAAH
AAEAAQAAAAMAAQABAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAMAAAACAAEAAQAAAAMAAQABAAAA
AgABAAQAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAA
AAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAABwABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEA
AAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAcAAQAB
AAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEA
AQAAAAIAAQABAAAAAQABAAEAAAAIAAEAAQAAAAEAAQABAAAAAQABAAEAAAABAAEAAQAAAAUAAQABAAAAAwAB
AAEAAAABAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAgAAAAIAAQABAAAAAQABAAEAAAAHAAEAAQAAAAMA
AQAFAAAAAQABAAUAAAABAAEABAAAAAIAAQAEAAAAAwABAAEAAAABAAEAAQAAAAgAAQABAAAAAQABAAEAAAAB
AAEAAQAAAAEAAQAEAAAAAgABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAA
AQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAMAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAA
AAMAAQABAAAAAQABAAEAAAAFAAEAAQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEA
AAAFAAEAAQAAAAMAAQABAAAAAQABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAACAAEAAgAAAAEAAQAB
AAAAAwABAAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAABAAEAAQAAAAMAAQABAAAAAQABAAEAAAAFAAEA
AQAAAAcAAQABAAAACQABAAEAAAABAAEAAQAAAAEAAQABAAAAAQABAAEAAAAFAAEAAQAAAAMAAQABAAAAAQAB
AAEAAAADAAEAAQAAAAMAAQABAAAAAwABAAEAAAADAAEAAQAAAAEAAQABAAAAAwABAAEAAAAHAAEAAQAAAAMA
AQABAAAAAQABAAEAAAADAAEAAQAAAAEAAQABAAAABQABAAEAAAAHAAEAAQAAAAoAAQABAAAAAQABAAEAAAAC
AAEABQAAAAEAAQAEAAAAAgABAAQAAAADAAEAAwAAAAIAAQABAAAAAwABAAEAAAACAAEABAAAAAMAAQABAAAA
AwAAAFQAAAAAAA==

というわけで、

sotarok & komikko、おめでとうございます!末永くお幸せに!

『プログラミングの魔導書 〜Programmers' Grimoire〜』がすごいのです

すでに多くの方がご存知のことと思いますが、株式会社ロングゲートからまもなく『プログラミングの魔導書 〜Programmers' Grimoire〜』が発刊されます。

発売前レビューをするということで、id:faith_and_brave さんより PDF を頂戴していたのですが、まったく自分の役割を自覚していなかった (内容のチェック的なものかと思ってました) ので、予約締切間近 (8/6、つまり明日までです!) ですが、ちょっと紹介させてもらいますね。

Bjarne Stroustrupへのインタビュー

デンマーク生まれの長男で髪の毛が年々少なくなっているというビャーネ・ストロストォプさんへのインタビューです。インタビュワーは、丸刈りで丸刈りでC++が好きで「本の虫」というブログを執筆されている、江添さんです。

C++0xの現況をざっくり把握するのに、どこを見るのが一番なんだろうか、と悩んでしまいますが、インタビューの書き起こしというのはとてもいいですね。これを読むだけでも価値があると思いました。

C++0x以降の進化について」というトピックで、ストロストォプさんは「multimethods」*1 を推しているのですが、これが個人的には興味深いところでした。

ダイナミックディスパッチをRTTIに基づいて行うのに、仮想関数を用いるのは、クラスの設計の柔軟性を妨げます。特に、値だけを保持するようなポリモーフィックなクラスに、ディスパッチのためだけに仮想関数を追加していくやり方は、ドメインロジックの記述を分散化させ、設計を困難にします。 (と、仕事をしていて思った。)

さらにストロストォプさんが指摘するように、確かにこの問題はVisitorパタンを使えば部分的に解決できますが、それでも、Visitorをacceptするためのメソッドを操作対象となるクラスにいちいち付加しないといけない、という問題がありますし、この状況で出現するVisitorというのは、ドメインロジックの構成要素ではないので、設計上の邪魔になります。

multimethodsを使うことで、ますますC++のコードが素人目にメンテ不可能になりそうな気もしないではありませんが、個人的には今すぐにでも欲しい機能です。

ところで、『ムーブ』についても触れていますね。「C++で書くと遅い!」というCプログラマの詭弁に最後まで抗えなかったのが、Cでは当たり前のムーブセマンティクスなのですが、これもムーブ演算子、ムーブコンストラクタの導入により、素直に、less error-proneな形で実現できるというのが気に入っています。

Boost.Serialization の紹介

オブジェクトを手軽に永続化するのに便利な Boost.Serialization の紹介です。残念ながら、私はちょっとしか使ったことがないのですが、記事もコンパクトにまとまっているように、予想以上に手軽に使えることは間違いないです。

Variadic Templates - お前を待っていた

メタプログラミングについて理解がないと、可変長テンプレートのありがたみというのは湧いてこない気もしていたのですが、この記事では、Win32 APIに基づいた実践的な例を出して解説しています。

Chronoライブラリで考える型システム

C++ では、型システムと、それを取り巻く操作を扱うのに、traits という概念が出てきますが、これって「圏」のイメージにつながるなあ、と思いました。

オーブンレンジクッキング

id:mb2sync さんの C++ ライブラリ集である P-Stade の一様を担う Oven についての詳細な解説記事。Range だから Oven。Oven とても仕事で使いたんですけど、Boost 標準的でないという理由でだめなんですよね…。RangeEx には期待したんですが、Oven のこなれた感には全く及んでおらず落胆しました。俺プロジェクトでは、バンバン使っていきたいです。

C++ を実践的に扱えるようになるのに、とても大事な概念だと思います。Range。Java でも Collections Framework が扱えて一人前みたいのがあったと思いますが、それに匹敵しますね。なので、これを機に Range に触れてみるというのはいい話だと思いますよ。

Hello, C++ World!

C++で『Hello, World』が実現されるまでの、男たちのドラマについて、稲葉さんが淡々と語ります。

C++ほどライブラリの作り手の心をくすぐる言語って他にないと思うんですよね。使い手には楽をさせてやろう、驚かせてやろう、という一心が、様々な技工と、数多くの非常に完成度の高いライブラリを生んできたのだと勝手に思っているわけですが、そんな一端が、ただの『Hello, World』に垣間見れた、という気がいたしました。

Crawling in the Stream

上のような感想を抱く理由は、まあ、iostream の設計を見るとよく分かるワケで、そうですね、やりすぎないようにするのが大事なのかな、と思います。

メタプログラミングノキワミ アッー!

sizeof() の使いどころ、Boost.Preprocessor の使いどころ、という感じでしょうか。メタプログラミングに馴染みのない方にとっては、ずっと頭の体操になってしまうかもしれないので、もうちょっと入門的な記事があってもいいのかな、と思ってしまいました。

Boost.AsioによるHTTP通信入門

ネットワークライブラリ、C界隈だと、libevent とか libev とか picoev とかいろいろあるわけですが、C++ ではどうなの、というわけで、Boost.Asio をオススメしたいと思っている今日この頃ですが、いかにせんドキュメントがない。そういう中で、この記事はかなりいいと思います。長すぎない、ちょうどいい分量のコードを使って解説がされている、という印象を受けました。

C++の歴史、BoostCon 2010 体験記

読み物ということでレビューは割愛しますw

まとめ

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

Effective C++ 原著第3版 (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)


Modern C++ Design―ジェネリック・プログラミングおよびデザイン・パターンを利用するための究極のテンプレート活用術 (C++ In‐Depth Series)

Modern C++ Design―ジェネリック・プログラミングおよびデザイン・パターンを利用するための究極のテンプレート活用術 (C++ In‐Depth Series)

  • 作者: アンドレイアレキサンドレスク,Andrei Alexandrescu,村上雅章
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2001/12
  • メディア: 単行本
  • 購入: 10人 クリック: 204回
  • この商品を含むブログ (99件) を見る

Exceptional C++―47のクイズ形式によるプログラム問題と解法 (C++ in‐Depth Series)

Exceptional C++―47のクイズ形式によるプログラム問題と解法 (C++ in‐Depth Series)

  • 作者: ハーブサッター,浜田光之,Harb Sutter,浜田真理
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/11
  • メディア: 単行本
  • 購入: 7人 クリック: 115回
  • この商品を含むブログ (65件) を見る

More Effective C++: 35 New Ways to Improve Your Programs and Designs (Addison-Wesley Professional Computing Series)

More Effective C++: 35 New Ways to Improve Your Programs and Designs (Addison-Wesley Professional Computing Series)

『Effective C++』『Modern C++ Design』『Exceptional C++』なんかを読んで、「はぁ、C++ってこういう言語だったんだ」と、C++を再発見したけれど、周りにそういう書き方実践している人いないし…と寂しい思いをしている人に。もちろん、これらの書籍を一通り読んだ後のために積ん読しておくのにも、おすすめです。

LLの虎 準決勝: 殺伐Python - 「タイガー&ドラゴン」

資料とパッチを公開します。

Puby の方の解説とスクリーンキャストは
Python の文法だけを Ruby っぽくしてみたらどうなるか実験した」にあります。

Rhython の方のパッチは
Gist:502964
です。

LLTigerで発表しました -Language Update編-

資料あげときます。

Xlib.php
http://github.com/moriyoshi/php-Xlib/
よりとってきてください。

<?php
require 'Xlib.php';
$fonts = array(
    'hiragino-mincho' => "/System/Library/Fonts/ヒラギノ明朝 ProN W6.otf",
    'courier'         => "/Library/Fonts/Courier New Bold.ttf",
);

$texts = array(
    array("はい"),
    array("始まって"),
    array("しまいました"),
    array("Heavyweight"),
    array("Heavyweight\nTemplate"),
    array("Heavyweight\nTemplate\nLanguage"),
    array("PHPの"),
    array("Language\nUpdate"),
    array("今回は"),
    array(""),
    array("moriyoshiが"),
    array("お送りさせて"),
    array("いただきます"),
    array("自己紹介"),
    array("<?php phpcredits(); ?>"),
    array(""),
    array("さて"),
    array("PHPは実用本意"),
    array("進歩"),
    array("とくにありません"),
    array("バグフィックス"),
    array("もり"),
    array("もり・"),
    array("もり・もり"),
    array("もり・もり・"),
    array("もり・もり・もりだくさん&#9832;"),
    array("…なわけですが"),
    array("昨年からの\n振り返り"),
    array("2009/6/30"),
    array("5.3.0リリース"),
    array("クロージャ導入\nネームスペース導入\netc."),
    array("\"),
    array("民主主義の悲劇"),
    array("…"),
    array("2009/7/16"),
    array("CVSから\nSubversionへの\n移行完了"),
    array("いまさらという感じ"),
    array("以降\nしばらく\n目立った動きなし"),
    array("…"),
    array("2010/3/12"),
    array("PHP6ブランチ放棄事件"),
    array("事の発端"),
    array("output-bufferingのバグ"),
    array("直すには大規模な\n改修が必要"),
    array("パッチはtrunkにある"),
    array("5.3にバックポートしたい"),
    array("5.3は安定ブランチ"),
    array("なので許されない"),
    array("trunkはUnicode対応作業中"),
    array("リリースは当分先"),
    array("他にも同様の事例あり"),
    array("しびれを切らしたJaniさん"),
    array("無断で"),
    array("trunkをリネームし\n5.3からtrunkを再ブランチ"),
    array("糾弾される"),
    array("そこにRasmusさん登場"),
    array("「まあまあ落ち着いて」"),
    array("一定の理解を示すよう提案"),
    array("結局"),
    array("Unicode化は見送られ"),
    array("新生trunk\n(5.4ブランチ)"),
    array("誕生"),
    array("というわけでした"),
    array("…"),
    array("2010/4/22"),
    array("Traits patchマージ"),
    array("Traitsとは?"),
    array("いわゆるMixin的なもの"),
    array("実装の再利用性を促進"),
    array("trait Hey {\n    public function hey() {\n        echo \"hey\\n\";\n    }\n}", array('font' => 'courier')),
    array("trait Howdy {\n    public function howdy() {\n        echo \"howdy\\n\";\n    }\n}", array('font' => 'courier')),
    array("class Hello {\n    use Hey, Howdy;\n}", array('font' => 'courier')),
    array("class Hello {\n    use Hey, Howdy;\n}\n\$hello = new Hello;\n\$hello->hey();\n\$hello->howdy();", array('font' => 'courier')),
    array("class TexasHello {\n    use Hey;\n    use Howdy { hello => howdy }\n}", array('font' => 'courier')),
    array("\$hello = new TexasHello;\n\$hello->howdy();\n\$hello->hello();", array('font' => 'courier')),
    array(""),
    array("2010/7/22"),
    array("5.3.3リリース"),
    array("重要な変更"),
    array("メソッド名=クラス名\n&#9758;コンストラクタ"),
    array("    ○\n(従来までの常識)"),
    array("but"),
    array("名前空間中のクラス"),
    array("×"),
    array("namespace Foo;\nclass Bar {\n    public function Bar() {\n        // 5.3.2まではコンストラクタ\n        // 5.3.3からは普通のメソッド\n    }\n}"),
    array("理由?"),
    array("嫌だから"),
    array(""),
    array("以上で"),
    array("Language Update"),
    array("駆け足でしたが"),
    array("ご清聴ありがとうございました"),
    array("ところで"),
    array("この"),
    array("プレゼンツールは"),
    array("もちろん"),
    array("100% Pure PHPで"),
    array("実装しております"),
    array("- おわり -"),
);

ini_set('memory_limit', -1);

$x = XDisplay::create();
$bc = $x->allocColor($x->screens[0]->colormap, 0xffff, 0xffff, 0xffff);
$wnd = $x->createWindow($x->screens[0]->rootWindow,
        0, 0, $x->screens[0]->width, $x->screens[0]->height, 0, XClient::InputOutput, null,
        array('backgroundPixel' => $bc['pixel'],
              'eventMask' => XClient::ExposureMask
                             | XClient::ButtonPressMask
                             | XClient::KeyPressMask
                             | XClient::StructureNotifyMask,
              'overrideRedirect' => true));
$_motif_wm_hints = $x->internAtom("_MOTIF_WM_HINTS");
$wnd->changeProperty(XClient::PropModeReplace, $_motif_wm_hints, $_motif_wm_hints, 32, "\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");

$gc = $wnd->createGC(
        array('function' => 3, 'lineWidth' => 8, 'lineStyle' => 0));
$x->mapWindow($wnd);

class Canvas {
    protected $im;
    public $width;
    public $height;
    protected $backgroundColor;
    protected $foregroundColor;

    public function __construct($width, $height) {
        $this->im = imagecreatetruecolor($width, $height);
        $this->width = $width;
        $this->height = $height;
        $this->backgroundColor = imagecolorallocate($this->im, 255, 255, 255);
        $this->foregroundColor = imagecolorallocate($this->im, 0, 0, 0);
    }

    public function toPixels() {
        ob_start();
        imagegd2($this->im, NULL, 64);
        $data = substr(ob_get_clean(), 23);
        $data |= substr($data & str_repeat("\x00\x00\x00\xff", strlen($data) / 4), 3);
        return $data;
    }

    public function clear() {
        imagefilledrectangle($this->im, 0, 0, $this->width, $this->height, $this->backgroundColor);
    }

    public function setFont($font) {
        $this->font = $font;
    }

    public function drawText($text, $sz) {
        for (;;) {
            $bbox = imagettfbbox($sz, 0, $this->font, $text);
            if ($bbox[2] < $this->width * 0.8)
                break;
            $sz -= 8;
        }
        imagettftext($this->im, $sz, 0, ($this->width - $bbox[2]) / 2, ($this->height - $bbox[3]) / 2 + $sz * 0.6, $this->foregroundColor, $this->font, $text);
    }
}
     
$cv = new Canvas($x->screens[0]->width, $x->screens[0]->height);

function drawImage($wnd, $gc, $x, $y, $width, $height, $data) {
    $ncx = (int)($width / 64);
    $ncy = (int)($height / 64);
    for ($j = 0; $j < $ncy; $j++) {
        $ch = min(64, $height - $j * 64);
        for ($i = 0; $i < $ncx; $i++) {
            $cw = min(64, $width - $i * 64);
            $wnd->putImage(2, $gc, $cw, $ch, $x + $i * 64, $y + $j * 64, 0, 24, substr($data, $i * 16384 + $j * $width * 256, 16384));
        }
    }
}

$data = null;
$counter = 0;
for (;;) {
    switch ($x->nextEvent($ev)) {
    case XClient::Expose:
        if ($data !== null)
            drawImage($wnd, $gc, 0, 0, $cv->width, $cv->height, $data);
        break;
    case XClient::ButtonPress:
    case XClient::KeyPress:
    case XClient::KeyRelease:
        $cv->clear();
        $line = array_shift($texts);
        if ($line) {
            $text = $line[0];
            $fontname = 'hiragino-mincho';
            if (isset($line[1])) {
                if (isset($line[1]['font']))
                    $fontname = $line[1]['font'];
            }
            $cv->setFont($fonts[$fontname]);
            $cv->drawText($text, 84);
        }
        $data = $cv->toPixels();
        drawImage($wnd, $gc, 0, 0, $cv->width, $cv->height, $data);
        break;
    case XClient::MotionNotify:
        break;
    }
}

pthread_once() の glibc における実装

ストアドを使ってWebスクレイピングしよう!」で作ったUDFでは、本来ステートレスに作らないといけないUDFを強引にステートフルに扱うために、MySQLが1コネクション1スレッドモデルであることをいいことにTLSを使ったのであるが、さて、TLSを使うためには、必ずTLSのキーを予め確保しておかないといけないから、初期化ルーチンが必要となるのだ。しかし、ELFの_init()やWin32DLLのDllMain()に頼ったりするのも、将来的に面倒なことになるかもしれないから、多少のオーバーヘッドは覚悟でpthread_once()を使うことにしたのだった。

しかし、この関数の挙動はよく考えるととても妙だ。中でグローバルロック持ってるんだろうけど、果たしてレースコンディションにちゃんと対応できるんだろうか。というわけで実装を見てみることに。

まずは、この辺かな?と思ったソース。
nptl/pthread_once.c:

/* Copyright (C) 2002, 2007 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include "pthreadP.h"
#include <lowlevellock.h>



static int once_lock = LLL_LOCK_INITIALIZER;


int
__pthread_once (once_control, init_routine)
     pthread_once_t *once_control;
     void (*init_routine) (void);
{
  /* XXX Depending on whether the LOCK_IN_ONCE_T is defined use a
     global lock variable or one which is part of the pthread_once_t
     object.  */
  if (*once_control == PTHREAD_ONCE_INIT)
    {
      lll_lock (once_lock, LLL_PRIVATE);

      /* XXX This implementation is not complete.  It doesn't take
	 cancelation and fork into account.  */
      if (*once_control == PTHREAD_ONCE_INIT)
	{
	  init_routine ();

	  *once_control = !PTHREAD_ONCE_INIT;
	}

      lll_unlock (once_lock, LLL_PRIVATE);
    }

  return 0;
}
strong_alias (__pthread_once, pthread_once)

これは、いわゆる Double-Checked Locking というパターンだ。このままでは、フェンスかロックかいれないとまずい。

バグかと騒ぐ前にいろいろコミットを追ってみたら、

nptl/sysdeps/unix/sysv/linux/x86_64/pthread_once.S があった。

/* Copyright (C) 2002, 2003, 2005, 2007, 2009 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   Contributed by Ulrich Drepper <drepper@redhat.com>, 2002.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

(中略)
__pthread_once:
(中略)
	testl	$2, (%rdi)
	jz	1f
	xorl	%eax, %eax
	retq

(中略)

	LOCK
	cmpxchgl %edx, (%rdi)
	jnz	5b

	/* Preserve the pointer to the control variable.  */
3:	pushq	%rdi
	cfi_adjust_cfa_offset(8)
	pushq	%rdi
	cfi_adjust_cfa_offset(8)

.LcleanupSTART:
	callq	*16(%rsp)
.LcleanupEND:

	/* Get the control variable address back.  */
	popq	%rdi
	cfi_adjust_cfa_offset(-8)

	/* Sucessful run of the initializer.  Signal that we are done.  */
	LOCK
	incl	(%rdi)
(後略)

ちゃんとLOCKプレフィックスいれてますね。

ストアドを使って、Webスクレイピングをしよう!

こんにちは、SQLを愛してやまないmoriyoshiです。

ストアドプロシージャは、一連のSQL文をサブルーチンのようにDBサーバに記録しておき、後からそれを呼び出すことができるようにする仕組みです。近代的なRDBMSには標準的に備わっている機能といえます。

制御構造などもSQL文で記述することができるので、結果的に、あらゆるロジックをSQLのみで記述することができます。手続き型プログラミングにどっぷり浸かった現場の方から愛用されていると言われています。

今回は、ストアドプロシージャの応用として、Webスクレイピングを行なってみましょう。Webスクレイピングとは、特定のWebサイトにアクセスし、そのページの内容 (HTML) を取得、解析し、必要な情報を取り出すという一連の操作を自動化することです。Webスクレイピングを効果的に活用すると、人間がブラウザに向かって単純作業を繰り返す必要がなくなり、労働生産性を向上することができます。

図書館の蔵書データを、スクレイピングしよう!

ここでは、例として、図書館の蔵書データを検索し、テーブルに格納するというストアドプロシージャを書いてみましょう。

本記事で利用する環境は

  • Ubuntu (ウブントゥ) Linux (バージョン 10.04)
  • MySQL (バージョン 5)

です。

MySQL は予めインストールされているものとして話を進めます。

スクレイピングに必要なUDF (ユーザ定義関数) モジュールをインストールする

MySQLは、本来Webスクレイピングのために造られているわけではありませんので、指定されたURLにアクセスして内容を取得する関数や、HTMLを解析する関数などを標準で持ってはいません。ですが、MySQLにはUDF (user defined functions)という仕組みがあり、任意の関数をプラグインという形で、後から追加することができます。

mysql-udf-curlのインストール

mysql-udf-curlは、次のような関数をMySQLに追加するモジュールです。

curl_fetch(URL, [リクエストメソッド, [リクエスト内容] ])
URLで指定された場所にアクセスし、内容を文字列として返します。
curl_esc(文字列)
指定の文字列に含まれるURL特殊文字 ("&" など) をエスケープします。
curl_setopt(オプション名, 文字列)
リクエストにあたって、オプションを設定します。オプション名に指定できるのは USERAGENT, PROXY, PROXYUSERPWD, PROXYUSER, PROXYPASSWORD, INTERFACE, USERPWD, USERNAME, PASSWORD, HTTPAUTH です。
  1. autoconf、automake、libtool、gitコマンドとcURLライブラリをインストールします。(パッケージ名はautoconf, automake, libtool, git-core, libcurl3-dev)
  2. http://github.com/moriyoshi/mysql-udf-curl.gitよりgitコマンドでソースコードを取得します。
  3. ソースコードが格納されたディレクトリに移動します。
  4. autogen.sh を実行します。
  5. configure を実行します。必要があれば、--with-mysql オプションを与え、mysql のインストールプレフィクスを指定します。
  6. make を実行します。
  7. make install を実行します。
mysql-udf-libxml2のインストール

mysql-udf-libxml2は、次のような関数をMySQLに追加するモジュールです。

xml_parse(XMLまたはHTML, [HTMLフラグ, [エンコーディング, [オプション] ] ])
XMLまたはHTMLをパースして、その結果の要素ツリーをメモリーに格納し、結果を表すハンドルを返します。HTMLフラグが1の場合、与えられた文字列がHTML文章であるとみなします。
xml_select(ハンドル, XPath)
ハンドルで指定された要素ツリーにおいて、XPathを評価し、その結果を文字列化したものを返します。
xml_free(ハンドル)
ハンドルで指定された要素ツリーをメモリーから削除します。
  1. autoconf、automake、libtool、gitコマンドとlibxml2ライブラリをインストールします。(パッケージ名はautoconf, automake, libtool, git-core, libxml2-dev)
  2. http://github.com/moriyoshi/mysql-udf-libxml2.gitよりgitコマンドでソースコードを取得します。
  3. MySQLのインクルードファイルがあるディレクトリ (${prefix}/include/mysql) に、MySQLソースコードから、my_tree.h と my_base.h をコピーします。
  4. ソースコードが格納されたディレクトリに移動します。
  5. autogen.sh を実行します。
  6. configure を実行します。必要があれば、--with-mysql オプションを与え、mysql のインストールプレフィクスを指定します。
  7. make を実行します。
  8. make install を実行します。

UDFを定義する

コマンドラインからmysqlコマンドを実行し、次の操作を行ないます。

CREATE FUNCTION curl_fetch RETURNS STRING SONAME 'curl.so';
CREATE FUNCTION curl_esc RETURNS STRING SONAME 'curl.so';
CREATE FUNCTION curl_setopt RETURNS INTEGER SONAME 'curl.so';
CREATE FUNCTION xml_parse RETURNS INTEGER SONAME 'xml-libxml2.so';
CREATE FUNCTION xml_select RETURNS STRING SONAME 'xml-libxml2.so';
CREATE FUNCTION xml_free RETURNS INTEGER SONAME 'xml-libxml2.so';

実際の操作は、次のような形で進行します。

mysql> CREATE FUNCTION curl_fetch RETURNS STRING SONAME 'curl.so';
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE FUNCTION curl_esc RETURNS STRING SONAME 'curl.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION curl_setopt RETURNS INTEGER SONAME 'curl.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION xml_parse RETURNS INTEGER SONAME 'xml-libxml2.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION xml_select RETURNS STRING SONAME 'xml-libxml2.so';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE FUNCTION xml_free RETURNS INTEGER SONAME 'xml-libxml2.so';
Query OK, 0 rows affected (0.00 sec)

ストアドプロシージャを作成する

次のようなファイルを作成し、適当な場所にsutoado.sqlという名前をつけて保存します。
このファイルは、libra_searchというストアドプロシージャを作成するものです。

DELIMITER //
CREATE PROCEDURE libra_search(kw CHAR(255))
BEGIN
  DECLARE PATH CHAR(255) DEFAULT '/html/body/div[@id="container"]/div[@id="contentOut"]/div[@id="content"]/table/tr';
  DECLARE rowpath CHAR(255) DEFAULT '';
  DECLARE hdl INTEGER DEFAULT 0;
  DECLARE count INTEGER DEFAULT 0;
  DECLARE i INTEGER DEFAULT 0;
  DECLARE dummy INTEGER DEFAULT 0;
  SET dummy = curl_setopt('USERAGENT', 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6');
  SET hdl=xml_parse(curl_fetch('http://www.library.okazaki.aichi.jp/tosho/asp/Book_Kensaku_g.asp', 'POST', CONCAT('hidKensakuF=1&Page=1&lang=jpn&sel1=1&key1=', curl_esc(kw), '&sel2=2&key2=&sel3=3&key3=&sel4=4&key4=&andor=0&SortKubun=1&isbn=&bookz=1&taiz=1&year-from=&year-to=&GengoKubun=no')), 1);
  SET count=xml_select(hdl, CONCAT('count(', PATH, ')'));
  SET i=2;
  WHILE i <= count DO
    SET rowpath=CONCAT(PATH, '[', i, ']');
    INSERT INTO libra (title, url, author, publisher, year, category) VALUES (
      xml_select(hdl, CONCAT(rowpath, '/td[2]')),
      xml_select(hdl, CONCAT(rowpath, '/td[2]/a/@href')),
      xml_select(hdl, CONCAT(rowpath, '/td[3]')),
      xml_select(hdl, CONCAT(rowpath, '/td[4]')),
      xml_select(hdl, CONCAT(rowpath, '/td[5]')),
      xml_select(hdl, CONCAT(rowpath, '/td[6]')));
     SET i=i+1;
  END WHILE;
  SET dummy = xml_free(hdl);
END//
DELIMITER ;

ストアドプロシージャを実行する前準備をする

次に、データベース testをなければ作成し、そこにlibraというテーブルを作成します。上記のストアドプロシージャは、libraテーブルにスクレイピング結果を格納します。

mysql> CREATE DATABASE IF NOT EXISTS test;
Query OK, 0 row affected (0.01 sec)
mysql> USE test;
Database changed
mysql> CREATE TABLE `libra` (
    -> `id` int(11) NOT NULL AUTO_INCREMENT,
    -> `title` varchar(255) DEFAULT NULL,
    -> `url` varchar(255) DEFAULT NULL,
    -> `author` varchar(255) DEFAULT NULL,
    -> `publisher` varchar(255) DEFAULT NULL,
    -> `year` date DEFAULT NULL,
    -> `category` varchar(255) DEFAULT NULL,
    -> PRIMARY KEY (`id`)
    -> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.05 sec)

ストアドプロシージャを読み込みます。

mysql> \. sutoado.sql
Query OK, 0 rows affected (0.00 sec)

ストアドプロシージャを呼び出す

ストアドプロシージャを呼び出すには、CALL 文を利用します。

CALL プロシージャ名(引数, ...)

実際に実行してみましょう。

mysql> CALL libra_search('テスト');
Query OK, 1 row affected, 7 warnings (53.49 sec)

mysql> SELECT * FROM libra\G
*************************** 1. row ***************************
       id: 1
    title:
BJT BUSINESS JAPANES
      url: ./syousai_g.asp?TosCode=00608049640
   author: 加藤 清方
publisher: 日本貿易振興機構
     year: 2020-06-02
 category: Y 810.7
*************************** 2. row ***************************
       id: 2
    title:
O Testamento
      url: ./syousai_g.asp?TosCode=00608038477
   author: Eric Van Lustbadre
publisher: Sextante
     year: 0000-00-00
 category: Y 933.7
*************************** 3. row ***************************
       id: 3
    title:
ITアーキテクトのやってはいけない 設計                    (日経BPムック)
      url: ./syousai_g.asp?TosCode=00608131810
   author: 日経SYSTEMS
publisher: 日経BP社
     year: 2020-09-12
 category:   336.5
*************************** 4. row ***************************
       id: 4
    title:
ITプロジェクトを成功に導くリスク・マネ
      url: ./syousai_g.asp?TosCode=00600195089
   author: 岡村 正司
publisher: 日経BP社
     year: 2020-08-03
 category: P 007.6
*************************** 5. row ***************************
       id: 5
    title:
愛の奇蹟 (ワンダー・オブ・ラヴ)
      url: ./syousai_g.asp?TosCode=00645010388
   author: リー ケイコ
publisher: Sony Records
     year: 0000-00-00
 category:   2A
*************************** 6. row ***************************
       id: 6
    title:
朝の通勤一時間で覚える語源別TOEICテ                    (講談社+α文庫)
      url: ./syousai_g.asp?TosCode=00161331302
   author: 小池 直己
publisher: 講談社
     year: 2020-00-01
 category: B 834
*************************** 7. row ***************************
       id: 7
    title:
朝の通勤一時間で覚えるTOEICテスト英                    (講談社+α文庫)
      url: ./syousai_g.asp?TosCode=00161203395
   author: 小池 直己
publisher: 講談社
     year: 0000-00-00
 category: B 834
*************************** 8. row ***************************
       id: 8
    title:
新しい学力テストを読み解く PISA/T
      url: ./syousai_g.asp?TosCode=00600243060
   author: 田中 耕治
publisher: 日本標準
     year: 2020-08-06
 category:   375.1
*************************** 9. row ***************************
       id: 9
    title:
あなたにこの問題が解けますか!! 和算術
      url: ./syousai_g.asp?TosCode=00500233223
   author: 柳谷 晃
publisher: 冬青社
     year: 2020-05-07
 category:   419.1
*************************** 10. row ***************************
       id: 10
    title:
あなたの愛の傷(トラウマ)テスト 4つの
      url: ./syousai_g.asp?TosCode=00500491975
   author: マックス・リュッシャー
publisher: 集英社
     year: 0000-00-00
 category:   141.6
*************************** 11. row ***************************
       id: 11
    title:
あなたの寿命予測テスト 長生きのためのア
      url: ./syousai_g.asp?TosCode=00500657665
   author: 松山 淳
publisher: すばる舎
     year: 2020-06-04
 category: H 498.3
*************************** 12. row ***************************
       id: 12
    title:
アメリカン・デス・トリップ              上
      url: ./syousai_g.asp?TosCode=00161498542
   author: ジェイムズ・エルロイ
publisher: 文芸春秋
     year: 2020-01-09
 category:   933.7
*************************** 13. row ***************************
       id: 13
    title:
アメリカン・デス・トリップ              下
      url: ./syousai_g.asp?TosCode=00161498551
   author: ジェイムズ・エルロイ
publisher: 文芸春秋
     year: 2020-01-09
 category:   933.7
*************************** 14. row ***************************
       id: 14
    title:
ありがとうポール・モーリア グレイテスト
      url: ./syousai_g.asp?TosCode=00645066258
   author: モーリア ポール
publisher: Universal
     year: 0000-00-00
 category:   5B
*************************** 15. row ***************************
       id: 15
    title:
あわてるにんタマ、テストでコケる!? ら                    (こどもおはなしラン
      url: ./syousai_g.asp?TosCode=00120306899
   author: 尼子 騒兵衛
publisher: ポプラ社
     year: 0000-00-00
 category:   913
*************************** 16. row ***************************
       id: 16
    title:
囲碁急所の一着 どちらに打つのが本手です                    (マンツーマン・ブッ
      url: ./syousai_g.asp?TosCode=00500310687
   author: 小山 靖男
publisher: 山海堂
     year: 2020-05-05
 category: N 795
*************************** 17. row ***************************
       id: 17
    title:
いちばんやさしいソフトウェアテストの本                      (技評SE新書)
      url: ./syousai_g.asp?TosCode=00600377742
   author: 石原 一宏
publisher: 技術評論社
     year: 2020-09-02
 category: I 007.6
*************************** 18. row ***************************
       id: 18
    title:
1級販売士キーワード解説138 資格取得
      url: ./syousai_g.asp?TosCode=00319474002
   author: 酒巻 貞夫
publisher: 同友館
     year: 2020-03-06
 category:   673.3
*************************** 19. row ***************************
       id: 19
    title:
井伏鱒二全集                            第6巻
      url: ./syousai_g.asp?TosCode=00160652938
   author: 井伏 鱒二
publisher: 筑摩書房
     year: 0000-00-00
 category:   918.6
*************************** 20. row ***************************
       id: 20
    title:
いま“人気の”&“必要な”商品・サービス2004
      url: ./syousai_g.asp?TosCode=00339986264
   author: &#160;
publisher: 日本消費者協会
     year: 2020-04-06
 category:   545.8
20 rows in set (0.00 sec)

おお、ちゃんとスクレイピングできましたね!

まとめ

ストアドプロシージャはもともと強力な機能ですが、そこにUDFを組み合わせることで、さらに可能性が広がるということがわかりました。

みなさんも、ストアドプロシージャを使って、いろいろなサイトをクロールしてみたりしてくださいね!
便利なUDFを作ったら、ぜひ教えてください!

ではまた!