PHP ob_get_contents "sometimes"当不应该返回时返回空


PHP ob_get_contents "sometimes" returns empty when it should not?

问题:我目睹了一个随机的情况下,ob_get_contents()返回什么,当它应该有东西。每天成千上万的成功中只有少数失败。随机.

基础:我使用输出缓冲将一个特定的HTML生成输出包装到一个变量中,并写入到文件中。然后将该文件分发给所有后续的点击X分钟,然后用新的HTML构建刷新该文件。它是一个基本的内联缓存构建器,添加到旧的站点代码上。

在看到空页面的一些问题后,我跟踪到ob_get_contents()在给定的刷新运行中没有返回任何内容。当它下次刷新时,通常是可以的。然后,突然地,几个小时后,空返回(从来没有在"同一时间")。

它快把我逼疯了,因为它不一致。当从ob_get_contents()返回为空时,我有php动作电子邮件给我…有很多细节。似乎没有什么能解释"为什么"。

在将代码的复杂版本简化到只保留其核心之后…这就是导致问题的全部原因:

ob_start();
// A lot of html generation code which would normally just output ...
// This html will ALWAYS have content ...
$guts = ob_get_contents();
if ( empty($guts) ) { /* email me a failure notice! */ }
ob_end_clean();
// write $guts to file and echo ...

其他细节:

  • PHP版本5.5.9-1ubuntu4.19(可能是这个版本有bug ?)
  • output_buffering 4096
  • ob_get_level()总是返回"2"
  • HTML生成范围从10KB到92KB,取决于哪一部分
  • 不总是出现在同一个HTML片段
  • 所有的点击都没有POST或GET参数传递。
  • 大多数是这类代理(所有随机ip):

    • "Ruby"
    • "Mo%20PTT/2016092702 CFNetwork/808.0.2 Darwin/16.0.0"
    • "FeedBurner/1.0"

请注意:它不是总是像其他关于ob_get_contents()的堆栈问题一样返回空。我读了一遍,没有帮助……我希望它一直都是,那么它将是一个明显的修复。

几个月来我一直在同一个PHP版本(5.5.9)上看到类似的问题。也不能切换到不同的PHP版本。我甚至很难在我们的系统中检测到这一点,但幸运的是,现在能够追踪并利用它。

在PHP 5.5.9中,print_r函数内部使用了输出缓冲,在这个版本中有关于print_r和输出缓冲的bug报告。

这就是你需要做的…

创建脚本first.php:

<?php
ignore_user_abort(true);// (curl disconnects after 1 second)
ini_set('max_execution_time','180');    // 3 minutes
ini_set('memory_limit','512M');         // 512 MB
function testPrint_r($length)
{
    $test1 = array('TEST'=>'SOMETHING');
    $test2 = print_r($test1, true);
    $test3 = "Array'n('n    [TEST] => SOMETHING'n)'n";
    if(strcmp($test2, $test3)!==0) {
        throw new Exception("Print_r check failed, output length so far: ".$length);
        // consult your error.log then, or use some other reporting means
    }
}
$message = "123456789'n";
$length = strlen($message);
while(1)
{
    echo $message;
    $total_length += $length;
    testPrint_r($total_length);
}
die('it should not get here');

创建另一个脚本second.php:

<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,    'http://some.server/first.php');
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_exec($ch);
curl_close($ch);
echo "all done";

这里发生了什么:

第一个脚本只是在循环中输出一些字符。它通过每次迭代回显10个字符来实现。如果你只是调用这个脚本,它将总是在配置的时间后超时。

第二个脚本使用CURL调用第一个脚本,但以断开连接的方式(1秒)。这就是为什么第一个脚本包含ignore user abort。

不知怎么的,很可能是由于php 5.5.9版本特有的一些错误,在大约1.8MB的数据已经被回显后,print_r等任何额外的输出缓冲使用中断。带第二个参数TRUE的Print_r什么也不返回。最有可能的是,一些内部或系统端缓冲区耗尽,其他字符无法放置在任何地方,或者已经回显的字符被丢弃。不知道。我无法从phpinfo中找到阈值与任何配置设置之间的相关性。输出缓冲在我们的系统中没有设置值。

我的建议

所以可能是使用了一些CURL/WGET并断开了连接,或者是使用了普通浏览器并在开始时断开了连接。像"Ruby","FeedBurner"这样的名字听起来像libs或机器人。

a)如果你的脚本不是太复杂,尽量避免在PHP 5.5.9中输出缓冲,print_r也是如此。Var_export很好,工作方式不同。

b)在生成输出时,将回显替换为字符串连接,直接用于文件写入。如果您使用smarty,那么这可能是不可能的,因为smarty在内部大量使用输出缓冲。

c)或者创建一个禁用代理的列表,如果它们是这些失败的主要原因。

d)顺便说一句。如果您的ob_get_level()返回2,这意味着系统中的输出缓冲在默认情况下是打开的。我认为你的任务不需要它,关掉它甚至可能对你有帮助。值得一试。

尝试在您的系统上运行相同的脚本,并告诉我最大是多少。在你的箱子里也有。

我想我通过将php_flag output_buffering On行添加到我的.htaccess文件来解决类似的问题。在我的例子中,我的PHP文件首先包含HTML代码,然后是调用ob_get_contents命令的PHP块。有时这个ob_get_contents的结果为空。我不确定这是否总是有效,或者为什么。

您可以使用一个cron作业来解决这个问题,该作业重建文件并仅在成功时保存它,并且服务器仅提供静态文件。这样你就会得到一个空页面。