PHPのストリームフィルタに流れるデータを覗く
こちらの記事の続きで、ストリームフィルタにどのようにデータが流れているのかを簡単に確認できるようにしてみます。初めてストリームフィルタを書く人など、どのようにデータが流れているかイメージしづらい場合に役に立つかもしれません。
今回作成したフィルタ(CaptureFilter)は以下のとおりです。このフィルタは入力Brigadeから受け取ったデータを加工せずそのまま出力Brigadeに渡します(ただの土管)。渡す際にBucketのデータを標準エラー出力に出力することで、Brigade内にいくつBucketが連なっているか、各Bucketのサイズや中身がどうなっているかを確認できるようにしています。
PHPで書いたフィルタなら、そこにデバッグ用のコードを挿入すれば同様のことはできますが、PHPの組み込みのフィルタ(convert.iconv等)に対してもその前後に挿入することで、どのようなデータを入出力しているかを確認することができます。
filter.php
<?php // Stream上のデータをキャプチャするフィルタ class CaptureFilter extends php_user_filter { private string $name = ''; public function filter($in, $out, &$consumed, $closing) { fprintf(STDERR, "CaptureFiter(%d):%s\n", $closing, $this->name); $output = 0; while ($bucket = stream_bucket_make_writeable($in)) { fprintf(STDERR, " bucket\n"); fprintf(STDERR, " datalen: $bucket->datalen\n"); fprintf(STDERR, " data: "); $hex = bin2hex($bucket->data); if (strlen($hex) > 32) { $hex = substr($hex, 0, 16) . ' ... ' . substr($hex, strlen($hex) - 16); } fprintf(STDERR, "%s\n", $hex); $consumed += $bucket->datalen; stream_bucket_append($out, $bucket); $output = 1; } // $inが空でもPSFS_PASS_ONを返して次のフィルターを必ず呼び出す。 return PSFS_PASS_ON; } function onCreate() { fprintf(STDERR, "onCreate(): %s\n", $this->filtername); if (preg_match('/^capture\.(.*)/', $this->filtername, $matches)) { $this->name = $matches[1]; } return true; } public function onClose() { fprintf(STDERR, "onClose(): %s\n", $this->filtername); } } stream_filter_register('capture', CaptureFilter::class); // noname stream_filter_register('capture.*', CaptureFilter::class); // named // optionでフィルターに名前を付けたい場合は、以下のように登録する。 // 複数のcaptureフィルターを登録する時に見分けがつくので便利。 // stream_filter_append($fp, 'capture.after');
フィルタチェーンへの登録は stream_filter_append($fp, 'capture') のようにしますが、オプションで名前を指定できるようにしてあります。
stream_filter_append($fp, 'capture.xxxxx') のようにドット区切りで名前を指定することで、複数のcaptureフィルタを挿入した場合に名前で識別できるようになります。前回の記事では触れていなかった onCreate() や onClose() を使っているので参考になればと思います。
以下の例では 入力データを bzip2.decompress で展開した後、文字コードをUTF8からSJISに変換して出力していますが、その各工程にcaptureフィルタを挿入してどのようなデータが入出力されているかを表示しています。
deflate.php
<?php include_once('./filter.php'); $params = [ 'blocks' => 9, 'work' => 0, ]; $fp = fopen('php://output', 'w'); stream_filter_append($fp, 'capture'); stream_filter_append($fp, 'bzip2.decompress', STREAM_FILTER_WRITE, $params); stream_filter_append($fp, 'capture.decompressed'); stream_filter_append($fp, 'convert.iconv.utf-8/cp932//TRANSLIT'); stream_filter_append($fp, 'capture.last'); while (!feof(STDIN)) { $data = fread(STDIN, 1000); if ($data === false) { break; } $ret = fwrite($fp, $data); if ($ret === false) { fprintf(STDERR, "Write error\n"); break; } fprintf(STDERR, "Writed %d\n", $ret); } fclose($fp);
deflate.phpで作成したフィルタチェーンの内容
↓ capture ↓ bzip2.decompress ↓ capture(decompressed) ↓ convert.iconv ↓ capture(last) ↓ stdout
実際に確認してみましょう。
使用例
$ echo "あいうえお" | bzip2 | php deflate.php > /dev/null onCreate(): capture onCreate(): capture.decompressed onCreate(): capture.last CaptureFiter(0): bucket datalen: 49 data: 425a683931415926 ... 9c284815f6e4c200 CaptureFiter(0):decompressed bucket datalen: 16 data: e38182e38184e38186e38188e3818a0a ← UTF-8の"あいうえお" CaptureFiter(0):last bucket datalen: 11 data: 82a082a282a482a682a80a ← SJISの"あいうえお" Writed 49 Writed 0 CaptureFiter(1): onClose(): capture onClose(): capture.decompressed onClose(): capture.last
各captureフィルタで以下のデータが流れていることを確認できます。
- 圧縮データ (capture)
- 展開後のデータ(UTF-8文字列) (capture.decompressed)
- SJIS変換後の文字列 (capture.last)
これはかなり単純な例ですが、bzip2.decompress への入力データが大きくなった場合は Bucket が分割されて、データの流れもややこしくなってきます。ドキュメントが少ないストリームフィルタの挙動を学ぶ上で役立てば幸いです。
[関連記事]
投稿日:2022/03/11 12:10
タグ: PHP