Home > ブログ > PHPのストリームフィルタに流れるデータを覗く

ブログ

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

Top

アーカイブ

タグ

Server (17) 作業実績 (15) プログラミング (13) ネットワーク (9) PHP (8) C (7) C++ (7) OpenSSL (5) Webアプリ (5) Nginx (5) laravel (4) Linux (4) 書籍 (3) AWS (3) JavaScript (3) Rust (2) Vue.js (2) Golang (2) EC-CUBE (2) OSS (1) MySQL (1) お知らせ (1) デモ (1) CreateJS (1) Apache (1)