pixiv Tech Meeting #1 にて発表してきました。

ひょんなことで pixiv といろいろ縁があったので発表に至りました。
PHP を JS に変換する「Phjosh (仮)」についてです。

誰にも聞かれなかったのであえて書くと、Phjoshの開発者の脳内発音は「fjosh」みたいな感じです。

GitHubでソースは公開しています。
まだできないことの方が多いので、暇を見て作り込んでいきます。

http://github.com/moriyoshi/phjosh

12/22 追記:
PHP/Parser/Base.php がないと言われますが、これは

kmyacc-forked をインストールして、
http://github.com/moriyoshi/kmyacc-forked

phjosh のディレクトリで make を実行すると生成されます。

java-ja.js #2 で『AAなゲームをJSで』を発表しました

動画をAAに変換するデモは以下で観れます。 (動画がTheoraなのでSafariは非対応)

http://dl.dropbox.com/u/673207/aalibtest/index.html

紹介したサンプルコードその1:

<html>
<body>
<pre id="screen" style="width:80ex; height:25em; line-height:100%; color:white; background-color:black"></pre>
<script type="text/javascript">
function repeat(c, n) { return new Array(n+1).join(c); }
var scr = document.getElementById("screen");
var lines = new Array(25);
var lineChars = repeat(" ", 80);
for (var i = 0; i < lines.length; i++) {
    var line = document.createTextNode(lineChars);
    lines[i] = line;
    scr.appendChild(line);
    scr.appendChild(document.createTextNode("\r\n"));
}
function update(x, y, c) {
    var line = lines[y];
    line.nodeValue = line.nodeValue.substring(0, x) + c + line.nodeValue.substring(x + c.length);
}

var x = 0, y = 0;
update(x, y, "*");
setInterval(function() {
    if (x < 80) {
        update(x, y, " ");
        x++;
        update(x, y, "*");
    }
}, 500);
</script>
</body>
</html>

サンプルコードその2:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<pre id="screen" style="width:80ex; height:25em; line-height:100%; color:white; background-color:black; font-family: Osaka, MS Gothic"></pre>
<script type="text/javascript">
function repeat(c, n) { return new Array(n+1).join(c); }
var scr = document.getElementById("screen");
var lines = new Array(25);
var lineChars = repeat(" ", 40);
for (var i = 0; i < lines.length; i++) {
    var line = document.createTextNode(lineChars);
    lines[i] = line;
    scr.appendChild(line);
    scr.appendChild(document.createTextNode("\r\n"));
}
function update(x, y, c) {
    var line = lines[y];
    line.nodeValue = line.nodeValue.substring(0, x) + c + line.nodeValue.substring(x + c.length);
}

function updateMultiple(x, y, c) {
    for (var i = 0; i < c.length; i++)
        update(x, y + i, c[i]);
}

var characterE = [
    "       ",
    "       ",
    "       ",
    "       ",
    "       "
];

var character = [
    " \ | / ",
    "  / ̄\  ",
    "─(゜∀゜)─",
    "  \_/  ",
    " / | \ "
];

var x = 0, y = 0;
updateMultiple(x, y, character);
setInterval(function() {
    if (x < 40 - 7) {
        updateMultiple(x, y, characterE);
        x++;
        updateMultiple(x, y, character);
    }
}, 200);
</script>
</body>
</html>

サンプルコードその3:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<pre id="screen" style="width:80ex; height:25em; line-height:100%; color:white; background-color:black; font-family: Osaka, MS Gothic"></pre>
<script type="text/javascript">
function repeat(c, n) { return new Array(n+1).join(c); }
var scr = document.getElementById("screen");
var lines = new Array(25);
var lineChars = repeat(" ", 40);
for (var i = 0; i < lines.length; i++) {
    var line = document.createTextNode(lineChars);
    lines[i] = line;
    scr.appendChild(line);
    scr.appendChild(document.createTextNode("\r\n"));
}
function update(x, y, c) {
    var line = lines[y];
    line.nodeValue = line.nodeValue.substring(0, x) + c + line.nodeValue.substring(x + c.length);
}

function updateMultiple(x, y, c) {
    for (var i = 0; i < c.length; i++)
        update(x, y + i, c[i]);
}

var characterE = [
    "       ",
    "       ",
    "       ",
    "       ",
    "       "
];

var character = [
    " \ | / ",
    "  / ̄\  ",
    "─(゜∀゜)─",
    "  \_/  ",
    " / | \ "
];

var c = [];
for (var i = 10; --i >= 0;) {
    var e = { x: Math.random() * (40 - 7) | 0, y: Math.random() * (25 - 5) | 0 }
    c.push(e);
    updateMultiple(e.x, e.y, character);
}
setInterval(function() {
    for (var i = c.length; --i >= 0; )
        updateMultiple(c[i].x, c[i].y, characterE);
    for (var i = c.length; --i >= 0; ) {
        c[i].x++;
        updateMultiple(c[i].x, c[i].y, character);
    }
}, 200);
</script>
</body>
</html>

Python Hack-a-thon で発表してきました!!

今回は資料の作成が後手に回ってしまい、運営の方々や発表者の皆様には大変迷惑をおかけしました。

さて、今回は ctypes 拡張モジュールに関する内容を発表させていただきました。

php-in-python の使い方

PHP-in-Pythonには、まだドキュメントがありません。インストール方法を簡潔に説明します。

a. Windowsの場合
  1. windows.php.netよりThread-safe版のPHPのバイナリをダウンロードします。
  2. ダウンロードしたアーカイブを適当なディレクトリに展開します。
  3. 展開された中身に含まれているphp5ts.dllの場所がPATH環境変数に含まれるようにします。
b. Linux等Free Unixの場合
  1. php.netよりPHPソースコードをダウンロードします。
  2. ソースコードを configure に --enable-maintainer-zts および --enable-embed=shared を渡す形でビルドします。
  3. make install します。
  4. インストールされた ${PREFIX}/lib/libphp5.so が共有ライブラリとして読み込めるように、${PREFIX}/lib が LD_LIBRARY_PATH に含まれるようにします。

以上の手順を踏んだ後で、

import php
print php.eval("1 + 2")

のようにすると、1 + 2 が PHPインタプリタで評価され、その結果が Python に返されます。

import php
print php.run("test.php")

のようにすると、test.php が実行されます。

リクエストパラメータを PHP の実行環境に渡したい場合は

import php
php.eval("var_dump($_GET['aaa'], $_POST['bbb']);", query_string="aaa=GET", content_type="application/x-www-form-urlencoded", post_data="bbb=POST")

のようにします。

おまけ

Mac OS X 上で、ctypes拡張モジュールを使ってダイアログボックスを出すコード。

import ctypes

kCFUserNotificationStopAlertLevel = 0
kCFUserNotificationNoteAlertLevel = 1
kCFUserNotificationCautionAlertLevel = 2
kCFUserNotificationPlainAlertLevel= 3
kCFUserNotificationNoDefaultButtonFlag = (1 << 5)
kCFUserNotificationUseRadioButtonsFlag = (1 << 6)
kCFStringEncodingUTF8 = 0x08000100

dll = ctypes.CDLL('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')

dll.CFStringCreateWithCString.restype = ctypes.c_void_p
dll.CFStringCreateWithCString.argtypes = (
    ctypes.c_void_p,
    ctypes.c_char_p,
    ctypes.c_int)

dll.CFRelease.argtypes = (ctypes.c_void_p,)

dll.CFUserNotificationDisplayNotice.argstype = (
    ctypes.c_double,
    ctypes.c_ulong,
    ctypes.c_void_p,
    ctypes.c_void_p,
    ctypes.c_void_p,
    ctypes.c_void_p,
    ctypes.c_void_p,
    ctypes.c_void_p)

dll.CFStringCreateWithCString.restype = ctypes.POINTER(ctypes.c_int)
title = dll.CFStringCreateWithCString(None, 'Hey', kCFStringEncodingUTF8)
message = dll.CFStringCreateWithCString(None, 'Hello, binary world.', kCFStringEncodingUTF8)

try:
    dll.CFUserNotificationDisplayNotice(
        ctypes.c_double(0.), kCFUserNotificationNoteAlertLevel,
        None, None, None, title, message, None)
finally:
    dll.CFRelease(title)
    dll.CFRelease(message)

Linux上で動くSkype用のbotを作る方法

はじめに

以前、知人のやっているBeProudという会社を手伝ったのですが、BeProudでは、エンジニアの主要なコミュニケーション手段としてSkypeが使われていました。当時、趣味でたまたまSkypeAPIについて調べていたので、悪戯っ気を出して、開発環境に即席でSkype APIを使ったbotを設置してみたところ、思いのほか好評を博し、いまやインフラと言っても過言ではない存在 *1 *2と化したようです。

まあそんな状況を眺めつつ、自宅のサーバにSkype botを設置して、お気に入りのSkypeチャットにもbotを加えてみたところ、これも結構好評だったので、興味ありそうな人向けに作成方法をまとめることにします。

Skype Public API

Skype Public APIとは、Skypeを外部からコントロールするためのインターフェイスです。

Skype Public APILinux上で簡単に利用するには、スクリプト言語バインディングであるSkype4PyPHP Skype API wrapperがおすすめですが、ここでは準公式な位置付けでAPIが充実しているSkype4Pyを使います。

Skype4Pyのインストール

Ubuntuであれば、multiverseに「python-skype」というパッケージ名で収録されているので、aptitude一発でインストールできます。OpenSUSEにも「python-Skype4Py」というパッケージがあるようです。

他のディストリビューションでも、setuptools経由で簡単にインストールできます。

$ easy_install Skype4Py

Skype4Pyを試す

最初は肩ならしにPythonのインタラクティブモードでSkype4Pyを使ってみることにします。

手元にまずXが立ち上がっていてSkypeが使える環境を用意してください。また、必要に応じてbot用のアカウントも取得しておきます。

Skypeを起動し、ユーザ名パスワードを入力して、サインインしたところで、ターミナルを開き、Pythonを立ち上げます。
f:id:moriyoshi:20100926102728p:image

Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

Skype4Pyをインポートして、Skypeオブジェクトを生成し、Attach()メソッドを呼びます。

>>> import Skype4Py
>>> skype = Skype4Py.Skype()
>>> skype.Attach()

最初にこの操作を行うとき、SkypeAPIを利用するアプリケーションを信用してもよいかを確認するプロンプトを出し、APIの呼び出し側はこのプロンプトに対して選択が行われるまでブロックします。ここでは、もちろん[はい]を選択します。
f:id:moriyoshi:20100926110238p:image

選択が終わると、再びPythonのインタラクティブモードに処理が戻ります。

手始めにコンタクト一覧を取得し、その名前を表示してみましょう。

>>> skype.Friends
(<Skype4Py.user.IUser object at 0x7f6cb3876890 with Handle=u'echo123'>, <Skype4Py.user.IUser object at 0x7f6cb38766d0 with Handle=u'moriyoshi_koizumi_2nd'>)
>>> for user in skype.Friends: print user.Handle, user.FullName
... 
echo123 Skype Test Call
moriyoshi_koizumi_2nd moriyoshi

次に、Skype Test User (echo123) に向かって何かメッセージを送信してみましょう。

>>> chat = skype.CreateChatWith("echo123")
>>> chat.SendMessage("test")
<Skype4Py.chat.IChatMessage object at 0x7f67bde5c690 with Id=82>
>>> chat.SendMessage(u"テスト")
<Skype4Py.chat.IChatMessage object at 0x7f67bde5c950 with Id=85>

チャットウィンドウを開くと、echo123に対してメッセージが送られ、同じ内容がecho123によってエコーバックされていることが確認できると思います。
f:id:moriyoshi:20100926112225p:image

では、メッセージ受信などのイベントが発生したときに特定の処理を行うイベントハンドラを試してみます。

>>> def handler(msg, event):
...     print event
... 
>>> skype.OnMessageStatus = handler
>>> chat.SendMessage("???")
<Skype4Py.chat.IChatMessage object at 0x7f67bde5c890 with Id=125>
>>> SENDING
SENT
RECEIVED
>>> def handler(msg, event):
...     if event == u"RECEIVED":
...         print msg.Body
...
>>> skype.OnMessageStatus = handler
>>> chat.SendMessage(u"\(^o^)/")
<Skype4Py.chat.IChatMessage object at 0x7f67bc484190 with Id=138>
>>> tof-bot 2010.09.26 02:33:10 \(^o^)/

以上を踏まえて、「やっぱり」というメッセージを受信すると「猫が好き」と返すbotを書いてみましょう。

# encoding: utf-8

import Skype4Py
import time

def handler(msg, event):
    if event == u"RECEIVED":
        if msg.Body == u"やっぱり":
            msg.Chat.SendMessage(u"猫が好き")

def main():
    skype = Skype4Py.Skype()
    skype.OnMessageStatus = handler
    skype.Attach()
    # イベントハンドラは別スレッドにて実行されるので、
    # 本スレッドではひたすらsleepしてスクリプトが終了しないようにしておく。
    while True:
        time.sleep(1) 

if __name__ == "__main__":
    main()

これを

$ python yappari.py

のように起動して、別マシンから「やっぱり」を送信してみましょう。次のスクリーンショットのようになれば成功です。

f:id:moriyoshi:20100926122004p:image

例がすみませんね昭和で。

と、まあ、どうでしょう、たったこれだけのAPIを覚えれば、botが作れてしまうということがわかったのではないかと思います。

Skypeをデーモン化する

と、これまでSkypeが予め立ち上げられたGUI環境上でSkype Public APIを利用する方法を説明しましたが、これではサーバに設置するのがやや面倒です。非GUI環境でSkypeを起動し、それを利用してbotを書くことはできないものでしょうか。そのような時に威力を発揮するのがXvfbです。Xvfbとは、その名前のvfbがvirtual framebufferから由来していることからもわかるように、フレームバッファ (画面への出力内容を蓄えておくメモリ) として、ビデオカードを使うのではなく、RAM上に確保した仮想フレームバッファを使うというXの実装です。これを使うことで、CUI環境下でXサーバを簡便に扱うことができます。

Xvfbの起動方法は簡単で、Xのディスプレイ番号を引数に与えるだけです。

$ Xvfb :20

デフォルトではスクリーンサイズが1280x1024でピクセルの深さが24となります。これを変更するときは

$ Xvfb :20 -screen 0 800x600x8

のように-screenオプションを指定します。一般に、画面サイズが小さければ小さいほど、ピクセルの深さが小さければ小さいほど、必要なフレームバッファのサイズが小さくなり、省メモリとなります。Skypeはかろうじて画面サイズ800x600、ピクセルの深さ8で動作させることができます。

Xvfb上で動くアプリケーションを外部から操作するには、VNCを利用します。VNCサーバの実装はいろいろありますが、コマンドラインから手軽に使える、各種ディストリビューションでパッケージが用意されているという点でx11vncをおすすめします。

x11vnc の利用方法も簡単で、

$ x11vnc -display :20

のようにXのディスプレイ番号を与えるだけです。デフォルトの接続ポート番号は5900番ですが、これを変更するには-rfbportオプションを指定します。

と、能書きはここまでにして、CentOSなどRedHat系のディストリビューションDebian系のディストリビューションで動くinitスクリプトをとりあえず貼ります。sudoを使っているので、デフォルトでインストールされていない環境にはあらかじめインストールしておいてください。

このスクリプトはDAEMON_USERで指定されたユーザでXvfbおよびskypeを起動または停止します。/etc/init.d以下にinitスクリプトとして設置して以下のように使います。

  • 起動
$ /etc/init.d/launch-skype.sh start
  • 停止
$ /etc/init.d/launch-skype.sh stop

initスクリプトがOS起動時に自動的に呼び出されるように設定する方法は、各ディストリビューションのドキュメントを参照してください。

起動ログは/var/log/skype以下に、xauth cookieは/var/run/skype以下に保存するので、あらかじめこれらのディレクトリをDAEMON_USERで読み書きできるように作成しておく必要があります。
Skypeでサインインするユーザ名とパスワードはそれぞれUSERNAMEとPASSWORDで指定しますので、ここを適宜編集してください。
XSERVERNUMはXvfbのディスプレイ番号を指定します。
DBPATHはSkypeが設定情報などを書き込む先のディレクトリです。これもDAEMON_USERで読み書きできるようにしておいてください。

このスクリプトで起動されたskypeの環境にx11vncでアクセスするには、次のようにします。

$ sudo -u skype x11vnc -xauth /var/run/skype/Xauthority
初期起動時の設定

初期起動時、VNCクライアントで接続すると次のような画面となっているはずです。

f:id:moriyoshi:20100927005410p:image

ここで[I agree]を選択すると、自動的にログインが開始されます。

f:id:moriyoshi:20100927005411p:image
f:id:moriyoshi:20100927005412p:image

次に、Skype4Pyをインタラクティブモードで起動して、Skype Public APIへのアクセス許可を設定します。
x11vncの起動で指定したように、envコマンドなどでDISPLAY環境変数とXAUTHORITY環境変数をあらかじめ設定した状態でpythonを起動する必要があります。

$ env DISPLAY=:20 XAUTHORITY=/var/run/skype/Xauthority python
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import Skype4Py
>>> skype = Skype4Py.Skype()
>>> skype.Attach()

前節で説明したように一旦Attach()でブロックした状態になりますので、ここでVNCを使ってプロンプトを操作します。
f:id:moriyoshi:20100927010152p:image

必ず[Remember this selection]にチェックを入れてから[Yes]を選択してください。でないと、botを起動するたび、毎度この操作をVNCから行わなくてはならなくなってしまいます。

以上で初期起動時の設定は完了です。

botを動かす

上の初期起動時の設定に出てきたコマンドラインをそのまま利用します。

$ env DISPLAY=:20 XAUTHORITY=/var/run/skype/Xauthority python bot.py

のようにして起動してください。

おわりに

Skype public APIやその利用方法について解説している日本語の情報があまりないため、敷居が高くなっていた印象を受けますが、この記事が少しでも足しになれば幸いです。

注意点として、Skypeは基本的にP2Pアプリケーションなので、一般的なチャットクライアントとは違い、勝手にさまざまなノードと接続を開始してしまいます。サーバで運用するときは、そのサーバの属するネットワークの管理者に管理ポリシーなどを確認したほうがよいでしょう。

ちなみに、今のところ、さくらのVPS、EC2でSkypeを動かすことができています。

あわせて読みたい:
PythonでSkypeにロギングする

*1:http://twitter.com/shin_no_suke/status/25658845058

*2:http://www.slideshare.net/bpstudy/bpstudy36-beproudbot-5319457

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、おめでとうございます!末永くお幸せに!