我建议您使用file_get_contents
andfile_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);
注意:我假设您打算将流复制到标准输出而不是“输出”,这在我的平台(达尔文)上不是有效的流。我还在脚本末尾关闭了流,这始终是一个好习惯。
这将创建一个超时为 1 的流,从fopen
被调用时开始。现在来测试你的三个条件。
验证行为
- 首先无法建立流。这可能应该在 fopen 调用中处理,而不是超时。
这可以正常工作——如果无法建立连接(服务器离线等),fopen
调用会立即触发警告。只需将脚本指向本地主机上的某个任意端口,该端口没有任何东西在监听。请注意,如果连接未成功建立,则fopen
返回 false。您必须在代码中检查它以避免使用 false 作为流。
- 流已建立,但没有数据传输。
这种情况也适用,只需使用您的正常 URL 运行脚本。这也使fopen
return 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
超时来解决此问题。