我目前正在实现一个PHP类,该类获取图像文件并将其缓存在本地。这些图像可能来自其他本地来源,通过HTTP或使用Guzzle客户端的HTTP。使用 PHP 流包装器,我应该能够以相同的方式处理所有源代码。
我现在正在尝试做的是,如果没有数据通过流传输,则实现超时。这应该处理以下情况:
- 首先无法建立流。这可能应该在
fopen
调用时处理,而不是超时。 - 流已建立,但不传输任何数据。
- 流已建立,数据传输,但在传输过程中会停止一段时间。
我想我可以用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 秒。请问有人可以解释一下吗?
我建议您使用file_get_contents
和file_put_contents
而不是流,它们支持所有包装器,您可以像将上下文传递给它们一样传递给它们fopen
.一般来说,它们更容易使用,因为它们返回并接受字符串而不是流。话虽如此,我不知道您的缓存机制的性质,如果流更适合您的用例,那么您将获得更多功能:)
问题所在
这里的问题似乎是对fopen
如何在阻塞模式下使用http
流包装器(直到我尝试之前我也没有完全理解)的误解。对于 GET(默认值),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",这在我的平台(达尔文)上不是有效的流。我还在脚本末尾关闭了 in 流,这始终是一种很好的做法。
这将创建一个超时为 1 的流,从调用fopen
时开始。现在测试你的三个条件。
验证行为
- 首先无法建立流。这可能应该在 fopen 调用时处理,而不是超时。
这按原样正常工作 - 如果无法建立连接(服务器脱机等),fopen
调用会立即触发警告。只需将脚本指向本地主机上的某个任意端口,没有任何内容在侦听。请注意,如果未成功建立连接,fopen
返回 false。您必须在代码中检查这一点,以避免使用 false 作为流。
流
- 已建立,但不传输任何数据。
此方案也有效,只需使用普通 URL 运行脚本即可。这也会使fopen
返回 false 并触发警告(不同的警告)。
流
- 已建立,数据传输,但在传输过程中会停止一段时间。
这是一个有趣的案例。要测试这一点,您可以编写一个脚本,将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
以解决此问题。
"读取超时">和"连接超时">之间有区别。
连接超时是建立初始连接(完成TCP连接握手)的超时。 读取超时是等待读取数据的超时。如果服务器在最后一个字节后 XX 秒未发送字节,则会生成读取超时错误。
即使您看到 5 秒的延迟(响应时间) - 这可能发生在初始连接(DNS 查找、连接等)期间,而不是在读取期间。