使用完整 URL 时 PHP file_get_content 非常慢

我正在使用一个脚本(我最初没有创建) ,从 HTML 页面生成一个 pdf 文件。问题是,现在处理这个问题需要很长的时间,比如1-2分钟。据推测,这种做法最初运行良好,但在过去几周内有所放缓。

该脚本在 php 脚本上调用 file_get_contents,然后将结果输出到服务器上的 HTML 文件中,并在该文件上运行 pdf 生成器应用程序。

我似乎已经将问题缩小到使用完整 URL 的 file_get_contents调用,而不是使用本地路径。

当我吸毒的时候

$content = file_get_contents('test.txt');

它几乎瞬间处理。但是,如果我使用完整的 url

$content = file_get_contents('http://example.com/test.txt');

需要30-90秒的时间来处理。

它不仅限于我们的服务器,它在访问任何外部 URL (如 http://www.google.com)时都很慢。我相信这个脚本调用完整的 URL 是因为有一些查询字符串变量是必需的,如果你在本地调用这个文件,这些变量就不起作用。

我还试了 fopenreadfilecurl,它们都同样慢。有什么办法能解决这个问题吗?

65897 次浏览

您能尝试从命令行获取服务器上的 URL 吗?我想起来了。如果它们以正常速度检索 URL,那么就不是网络问题,很可能是 apache/php 设置中的问题。

我将使用 卷曲()来获取外部内容,因为这比 file_get_contents方法快得多。不确定这是否能解决问题,但值得一试。

还要注意,服务器的速度将影响检索文件所需的时间。

下面是一个用法的例子:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://example.com/test.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);

注意: 这在 PHP 5.6.14中已经修复。现在,即使对于 HTTP/1.0请求,也会自动发送 Connection: close标头。参见提交 4b1dff6.

我花了很长时间才找到 file _ get _ content 脚本运行缓慢的原因。

通过使用 Wireshark 进行分析,问题(在我的例子中,可能也是你的例子)在于远程 Web 服务器直到15秒钟(即“ keep-alive”)才关闭 TCP 连接。

事实上,file _ get _ content 不会发送一个“连接”HTTP 头,所以远程 Web 服务器默认认为它是一个保持活动的连接,并且直到15秒钟才关闭 TCP 流(它可能不是一个标准值——取决于服务器 conf)。

如果 HTTP 有效负载长度达到响应 Content-Llength HTTP 头中指定的长度,普通浏览器会认为页面已经完全加载。File _ get _ content 不这样做,这是一个遗憾。

解决方案

因此,如果你想知道解决方案,这里是:

$context = stream_context_create(array('http' => array('header'=>'Connection: close\r\n')));
file_get_contents("http://www.something.com/somepage.html",false,$context);

这只是针对 告诉远程 Web 服务器在下载完成时关闭连接的,因为 file _ get _ content 不够智能,不能使用响应 Content-Llength HTTP 头自己完成。

有时候,这是因为 DNS 在你的服务器上太慢了,试试这个:

更换

echo file_get_contents('http://www.google.com');

作为

$context=stream_context_create(array('http' => array('header'=>"Host: www.google.com\r\n")));
echo file_get_contents('http://74.125.71.103', false, $context);

我也有同样的问题,

唯一对我有效的方法是在 $options数组中设置超时。

$options = array(
'http' => array(
'header'  => implode($headers, "\r\n"),
'method'  => 'POST',
'content' => '',
'timeout' => .5
),
);
$context = stream_context_create(array('http' => array('header'=>'Connection: close\r\n')));
$string = file_get_contents("http://localhost/testcall/request.php",false,$context);

时间: 50976毫秒(平均5次)

$ch = curl_init();
$timeout = 5;
curl_setopt($ch, CURLOPT_URL, "http://localhost/testcall/request.php");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
echo $data = curl_exec($ch);
curl_close($ch);

时间: 46679毫秒(平均5次)

注意: request.php 用于从 mysql 数据库中获取一些数据。

我有一个通过 API 传递的巨大数据,我使用 file_get_contents来读取数据,但它需要大约 60秒。然而,使用 KrisWebDev 的解决方案需要大约 25秒

$context = stream_context_create(array('https' => array('header'=>'Connection: close\r\n')));
file_get_contents($url,false,$context);

使用 Curl 我还要考虑的一点是,您可以“线程化”请求。这对我有很大的帮助,因为我目前还没有访问允许线程化的 PHP 版本。

例如,我使用 file _ get _ content 从远程服务器获取7个图像,每个请求需要2-5秒。当用户等待 PDF 生成时,这个过程本身就增加了30秒左右的时间。

这从字面上减少了时间约1图像。另一个例子是,我验证了36个 url,而在此之前我只做了一个 url。我想你明白我的意思了。:-)

    $timeout = 30;
$retTxfr = 1;
$user = '';
$pass = '';


$master = curl_multi_init();
$node_count = count($curlList);
$keys = array("url");


for ($i = 0; $i < $node_count; $i++) {
foreach ($keys as $key) {
if (empty($curlList[$i][$key])) continue;
$ch[$i][$key] = curl_init($curlList[$i][$key]);
curl_setopt($ch[$i][$key], CURLOPT_TIMEOUT, $timeout); // -- timeout after X seconds
curl_setopt($ch[$i][$key], CURLOPT_RETURNTRANSFER, $retTxfr);
curl_setopt($ch[$i][$key], CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch[$i][$key], CURLOPT_USERPWD, "{$user}:{$pass}");
curl_setopt($ch[$i][$key], CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($master, $ch[$i][$key]);
}
}


// -- get all requests at once, finish when done or timeout met --
do {  curl_multi_exec($master, $running);  }
while ($running > 0);

然后检查结果:

            if ((int)curl_getinfo($ch[$i][$key], CURLINFO_HTTP_CODE) > 399 || empty($results[$i][$key])) {
unset($results[$i][$key]);
} else {
$results[$i]["options"] = $curlList[$i]["options"];
}
curl_multi_remove_handle($master, $ch[$i][$key]);
curl_close($ch[$i][$key]);

然后关闭文件:

    curl_multi_close($master);

我知道这是一个老问题,但我今天发现它和答案不适合我。我没有看到任何人说每个 IP 的最大连接可以设置为1。这样,您正在执行 API 请求,而 API 正在执行另一个请求,因为您使用的是完整的 URL。这就是为什么直接从光盘加载可以工作的原因。对我来说,这解决了一个问题:

if (strpos($file->url, env('APP_URL')) === 0) {
$url = substr($file->url, strlen(env('APP_URL')));
} else {
$url = $file->url;
}
return file_get_contents($url);