我目前正在实现一个PHP类,它获取图像文件并在本地缓存它们.这些图像可能来自其他本地来源,通过HTTP或使用Guzzle客户端通过HTTP.使用PHP流包装器,我应该能够以相同的方式处理所有源.
如果没有数据通过流传输,我现在要做的就是实现超时.这应该处理以下情况:
>首先无法建立流.这可能应该在fopen调用时处理,而不是超时.
>已建立流但未传输任何数据.
>流已建立,数据已传输但在传输过程中会停止一段时间.
我想我可以用stream_set_timeout做到这一切,但我不太清楚这实际上是做什么的.如果流上的任何操作花费的时间超过允许的时间,那么超时是否适用,即我可以做一些需要0.5秒并且超时为0.75秒的事情?或者仅在没有数据通过流传输的时间超过允许时间时才适用?
我尝试使用这个简短的脚本测试行为:
$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秒,但我总是在1秒的超时时间内得到假.请有人解释一下吗?
解决方法:
我建议您使用file_get_contents和file_put_contents而不是流,它们支持所有包装器,您可以将上下文传递给它们,就像您可以fopen一样.它们通常更容易使用,因为它们返回并接受字符串而不是流.话虽这么说,我不知道你的缓存机制的性质,如果流更适合你的用例,更多的权力给你:)
问题
这里的问题似乎是误解了fopen如何在阻塞模式下使用http流包装器(在我试用之前我还没有完全理解).对于GET(the default),fopen似乎在调用时执行HTTP请求,而不是在读取流时.这可以解释为什么stream_set_timeout不能按预期运行,因为它在调用fopen后修改了流上下文.
解决方案
值得庆幸的是,有一种方法可以在调用fopen之前修改超时,而不是;你可以用上下文调用fopen.将从stream_context_create(作为Sammitch链接)返回的上下文传递给所有三种情况的fopen超时.作为参考,这是您的脚本将被修改的方式:
$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时开始.现在来测试你的三个条件.
验证行为
The stream cannot be established in the first place. This should probably be handled at the fopen call and not with a timeout.
这样可以正常工作 – 如果无法建立连接(服务器脱机等),fopen调用会立即触发警告.只需将脚本指向localhost上的任意端口,即没有任何内容正在侦听.请注意,如果未成功建立连接,则fopen返回false.您必须在代码中检查它,以避免将false用作流.
The stream is established but no data is transferred.
此方案也可以,只需使用普通URL运行脚本即可.这也使fopen返回false并触发警告(另一个).
The stream is established, data is transferred but it stops some time during transfer.
这是一个有趣的案例.要测试这个,你可以编写一个脚本,发送Content-Length和其他一些标题以及一些部分数据,然后等到超时,即:
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以解决此问题.
标签:php,php-stream-wrappers
来源: https://codeday.me/bug/20190611/1216276.html