【问题标题】:What is a bucket brigade?什么是水桶旅?
【发布时间】:2014-11-24 11:00:28
【问题描述】:

我真的很想实现php_user_filter::filter()。但因此我必须知道 bucket 旅 是什么。这似乎是我可以使用 stream_bucket_* 函数操作的资源。但是文档并没有真正的帮助。我能找到的最好的是stream_filter_register() 中的那些例子。

我特别好奇stream_bucket_new()stream_bucket_make_writeable() 能做什么。


更新:PHP 似乎暴露了 internal data structure of Apache

【问题讨论】:

标签: php php-stream-wrappers


【解决方案1】:

啊,欢迎阅读 PHP 手册中文档最少的部分! [我打开了一个关于它的错误报告;也许这个答案将有助于记录它:https://bugs.php.net/bug.php?id=69966]

斗式旅

从您最初的问题开始,bucket brigade 只是名为 userfilter.bucket brigade 的资源的名称。

将两个不同的旅作为第一个和第二个参数传递给php_user_filter::filter()。第一个旅是您从中读取的输入桶,第二个旅最初是空的;你给它写信。

关于你对数据结构的更新……它实际上只是一个带有字符串的双向链表。但很可能这个名字是从那里偷来的;-)

stream_bucket_prepend() / stream_bucket_append()

stream_bucket_prepend(resource $brigade, stdClass $bucket): null
stream_bucket_append(resource $brigade, stdClass $bucket): null

预期的$brigade 是输出旅,也就是php_user_filter::filter() 上的第二个参数。

$bucket 是一个 stdClass 对象,就像 stream_bucket_make_writable()stream_bucket_new() 返回的对象一样。

这两个函数只是将传递的桶预先或附加到旅。

stream_bucket_new()

为了揭开这个函数的神秘面纱,首先分析一下它的函数签名是什么:

stream_bucket_new(resource $stream, string $buffer): stdClass

第一个参数是您要写入此存储桶的$stream。其次是这个新存储桶将包含的$buffer

[这里要注意的是$stream参数其实不是很重要;它只是用来检查我们是否需要持久分配内存以便它在请求中存活下来。我只是假设当在非持久过滤器上操作时,您可以通过在此处传递持久流来使 PHP 很好地进行段错误...]

现在创建了一个 userfilter.bucket 资源,该资源分配给名为 bucket 的 (stdClass) 对象的属性。 该对象还有两个其他属性:datadatalen,它们包含缓冲区和此存储桶的缓冲区大小。

它将返回一个stdClass,您可以将其传递给stream_bucket_prepend()stream_bucket_append()

stream_bucket_make_writable()

stream_bucket_make_writeable(resource $brigade): stdClass|null

它将第一个存储桶从$brigade 移出并返回。如果$brigade 被清空,则返回null

补充说明

php_user_filter::filter() 被调用时,对象filter() 上的$stream 属性将被设置为我们当前正在处理的流。这也是您在调用它时需要传递给stream_bucket_new() 的流。 ($stream 属性将在调用后再次取消设置。您不能在例如php_user_filter::onClose() 中重复使用它。

还请注意,即使返回了 $datalen 属性,也无需设置该属性,以防在将 $data 属性传递给 stream_bucket_prepend()stream_bucket_append() 之前更改它。

该实现要求您(嗯,它期望或将引发警告)在返回之前从 $in 存储桶中读取所有数据。

文档对我们撒谎的另一种情况:在php_user_filter::onCreate() 中,$stream 属性没有设置。它只会在filter() 方法调用期间设置。

一般来说,不要对非阻塞流使用过滤器。我试过一次,结果大错特错……And it's not likely that's ever going to be fixed...

总结(示例)

让我们从最简单的情况开始:写回我们输入的内容。

class simple_filter extends php_user_filter {
    function filter($in, $out, &$consumed, $closing) {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}

stream_filter_register("simple", "simple_filter")

这里发生的所有事情都是从$in bucket brigade 获取桶并将其放回$out bucket brigade。

好的,现在尝试操纵我们的输入。

class reverse_filter extends php_user_filter {
    function filter($in, $out, &$consumed, $closing) {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += $bucket->datalen;
            $bucket->data = strrev($bucket->data);
            stream_bucket_prepend($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}

stream_filter_register("reverse", "reverse_filter")

现在我们注册了reverse:// 协议,它可以反转您的字符串(每个写入都在此处自行反转;写入顺序仍然保留)。因此,我们现在显然需要操作存储桶数据并将其添加到此处。

现在,stream_bucket_new() 的用例是什么?通常你可以追加到$bucket->data;是的,您甚至可以将所有数据连接到第一个桶中,但是当flush()'ing 时,桶旅中可能没有任何内容并且您想发送最后一个桶,那么您需要它。

class append_filter extends php_user_filter {
    public $stream;

    function filter($in, $out, &$consumed, $closing) {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        // always append a terminating \n
        if ($closing) {
            $bucket = stream_bucket_new($this->stream, "\n");
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}

stream_filter_register("append", "append_filter")

有了它(以及关于 php_user_filter class 的现有文档),人们应该能够通过将所有这些强大的可能性组合成更强大的代码来执行各种神奇的用户空间流过滤。

【讨论】:

  • 我非常感谢您为这个答案付出的努力,但对我来说,它仍然没有解释什么是桶旅 以及为什么需要这种方法处理(过滤)流。 OP 关于 Apache 旅的链接读起来很有说服力,但我想知道您是否可以确认 确实是使用这种“桶旅”方法的原因。
  • 如前所述,斗旅是一种资源。该资源在引擎盖下是一个有序的桶/字符串列表。 (您可以通过这些功能从中提取/添加。)我无法确认这是否是为什么使用它,您必须询问流过滤器实现的作者 Wez Furlong .我只能猜测,所以我不比你更清楚。另外,我想知道你为什么想知道确切地?我不确定这对您实施过滤器有何帮助...
  • 好吧,通过关于 Apache 旅的链接,看起来好像不能/不应该向存储桶写入比其初始长度更多的数据,如果有更多数据,则应该创建一个新存储桶需要将其放入输出流中,而不是放入输入流中。如果是这种“桶式组合”方法的全部意义,那么这是相当有价值的信息,不是吗?
  • 我没有仔细研究过 Apache 桶旅;至少实现(lxr.php.net/xref/PHP_5_6/ext/standard/user_filters.c#470)直接将我们拥有的完整操作字符串复制回存储桶。我想我已经间接暗示我的回答是可能的。 [“您甚至可以将所有数据连接到第一个存储桶中”]。在谈论 PHP 扩展时,这可能是桶组合的重点,如果它们不必进行额外的分配,那么它们可以更有效地工作;但是php_user_filter 只是以安全的方式直接暴露内部API。
  • @DecentDabbler 我想补充一点,内部 API 看起来很像那个 apache 网站上描述的内容。甚至某些函数名称等也非常相似。但是这些函数都只是暴露给扩展而不是用户态 PHP。 php_user_filter 非常(安全)在内部实现之上对内部实现的剽窃。所以,是的,我认为最终可以肯定地说它源自 apache 旅。
【解决方案2】:

我想我会提供一些背景信息。

首先,术语桶和旅。原来有一种叫做水桶大队的东西......有点像打火的标签团队......你有一连串的人站着不动,但把水桶递给他们旁边的人,产生一个恒定的装满水的桶流。

另外,如上所述,PHP 对 buckets 和 brigades 的采用来自 Apaches [Buckets and Brigades](http://www.apachetutor.org/dev/brigades],也许...对方法和推理给出了很好的解释。

但本质上的想法是,如果您需要在发送之前对某些内容进行修改,那么在中间流中进行修改有很多好处,尤其是当您使用桶和旅对流进行建模时。

【讨论】:

    猜你喜欢
    • 2017-07-12
    • 2013-12-17
    • 2020-08-26
    • 1970-01-01
    • 1970-01-01
    • 2016-08-03
    • 1970-01-01
    • 1970-01-01
    • 2011-05-25
    相关资源
    最近更新 更多