如果没有数据传输,PHP流超时

时间:2018-03-01 16:17:40

标签: php php-stream-wrappers

我目前正在实现一个PHP类,它可以获取图像文件并在本地缓存它们。这些图像可能来自其他本地来源,通过HTTP或使用Guzzle客户端通过HTTP。使用PHP流包装器,我应该能够以相同的方式处理所有源。

如果没有数据通过流传输,我现在要做的就是实现超时。这应该处理以下情况:

  1. 首先无法建立流。这可能应该在fopen调用时处理,而不是超时。
  2. 已建立流,但未传输任何数据。
  3. 建立流,传输数据但在传输过程中停止一段时间。
  4. 我想我可以用stream_set_timeout完成所有这些工作,但我并不清楚这实际上是做什么的。如果流上的任何操作花费的时间超过允许的时间,那么超时是否适用,即我可以做一些需要0.5秒并且超时为0.75秒的事情?或者它是否仅适用于没有数据通过流传输的时间超过允许的时间?

    我尝试使用这个简短的脚本测试行为:

    <?php
    
    $in = fopen('https://reqres.in/api/users?delay=5', 'r');
    $out = fopen('out', 'w');
    
    stream_set_timeout($in, 1);
    stream_copy_to_stream($in, $out);
    
    var_dump(stream_get_meta_data($in)['timed_out']);
    

    虽然reqres.in的响应延迟了5秒,但我总是得到false,超时为1秒。请有人解释一下吗?

2 个答案:

答案 0 :(得分:5)

我建议您使用file_get_contentsfile_put_contents而不是流,它们支持所有包装器,您可以将上下文传递给它们,就像fopen一样。它们通常更容易使用,因为它们返回并接受字符串而不是流。话虽这么说,我不知道你的缓存机制的性质,如果流对你的用例更好,对你更有力量:))

问题

这里的问题似乎是误解了fopen如何在阻塞模式下使用http流包装器(在我尝试之前我还没有完全理解)。对于GET(the default),fopen似乎在通话时执行HTTP请求,在读取流时。这可以解释为什么stream_set_timeout无法按预期运行,因为它会在调用fopen后修改流上下文。

解决方案

幸运的是,有一种方法可以在调用fopen之前修改超时,而不是;您可以使用上下文调用fopen。对于所有三种情况,将stream_context_create(与Sammitch链接)返回的上下文正确地传递给fopen超时。作为参考,这是您的脚本的修改方式:

<?php

$ctx = stream_context_create(['http' => [
        'timeout' => 1.0,
]]);

$in = fopen('https://reqres.in/api/users?delay=5', 'r', false, $ctx);
$out = STDOUT;

stream_copy_to_stream($in, $out);
var_dump(stream_get_meta_data($in)['timed_out']);
fclose($in);

注意:我假设您打算将流复制到stdout而不是“out”,这不是我平台上的有效流(Darwin)。我也在脚本结尾处输入了流,这总是很好的做法。

这将创建一个超时为1的流,从调用fopen开始。现在来测试你的三个条件。

验证行为

  
      
  1. 首先无法建立流。这可能应该在fopen调用时处理,而不是超时。
  2.   

这样可以正常工作 - 如果无法建立连接(服务器脱机等),fopen调用会立即触发警告。只需将脚本指向localhost上的任意端口,即没有任何内容正在侦听。 请注意,如果未成功建立连接,fopen将返回false。您必须在代码中检查该内容,以避免将false用作流。

  
      
  1. 已建立流,但未传输任何数据。
  2.   

这种情况也适用,只需使用普通网址运行脚本即可。这也使fopen返回false并触发警告(另一个)。

  
      
  1. 建立流,传输数据但在传输过程中停止一段时间。
  2.   

这是一个有趣的案例。要测试这个,你可以编写一个脚本,发送Content-Length和其他一些标题以及一些部分数据,然后等到超时,即:

<?php
header('Content-Type: text/plain');
header('Content-Length: 10');
echo "hi";
ob_flush();
sleep(10);

在睡眠和脚本退出之前,ob_flush是使PHP写入输出(不关闭连接)所必需的。您可以使用php -S localhost:port投放此邮件,然后将其他脚本指向localhost:port。在这种情况下,客户端脚本不会发出警告,fopen实际上会将元数据集中timed_out的流返回true。

结论

stream_set_timeout无法使用HTTP GET请求,fopen 处于阻止模式,因为fopen在调用请求时执行请求,而不是等待读取这样做。您可以使用超时将上下文传递给fopen以解决此问题。

答案 1 :(得分:2)

“读取超时”“连接超时”之间存在差异 ..

连接超时是进行初始连接的超时(完成TCP连接握手)。 读取超时是等待读取数据的超时。如果服务器在最后一个字节后XX秒没有发送字节,则会生成读取超时错误。

即使您看到延迟(响应时间)为5秒 - 这可能发生在初始连接(DNS查找,连接等)期间,而不是在您阅读期间。