PHP ob_get_contents «иногда» возвращает пустой, когда не должен?

#php

#php

Вопрос:

Проблема: я наблюдаю случайную ситуацию, когда ob_get_conents() ничего не возвращает, когда у него ДОЛЖНО быть что-то. Сбой нескольких из тысяч успехов каждый день. Случайным образом.

Основы: я оборачиваю определенный вывод генерации HTML в переменную, используя буферизацию вывода, и записываю в файл. Затем этот файл передается всем последующим обращениям в течение X минут, прежде чем он обновит файл новой сборкой HTML. Это базовый встроенный конструктор кэша, привязанный к более старому коду сайта.

После того, как я увидел несколько проблем с пустой страницей, я обнаружил, что ob_get_contents() ничего не возвращал для данного запуска обновления. Когда он будет обновляться в следующий раз, обычно все было в порядке. Затем, как гром среди ясного неба, empty возвращается снова через несколько часов (также никогда в «одно и то же время»).

Это сводит меня с ума, потому что это не согласуется. У меня есть действие php, напишите мне, когда возврат из ob_get_contents() пуст… с кучей деталей. Кажется, ничто не проливает свет на «почему».

После сокращения сложной версии кода до его ядра … это все, что вызывает проблему:

 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 (возможно, ошибка в этой версии?)
  • output_buffering 4096
  • ob_get_level() всегда возвращает «2»
  • Генерация HTML варьируется от 10 КБ до 92 КБ в зависимости от того, какая часть
  • Не всегда встречается в одном и том же фрагменте HTML
  • Все были обращениями, в которых не было переданных аргументов POST или GET.
  • Большинство из них являются агентами такого типа (все случайные IP-адреса):

    • «Ruby»
    • «Mo PTTT/2016092702 CFNetwork/808.0.2 Darwin /16.0.0»
    • «FeedBurner / 1.0»

Пожалуйста, обратите внимание: он не всегда возвращает пустой, как другие вопросы стека об ob_get_contents() . Я перечитал их, никакой помощи… Я бы хотел, чтобы это было всегда, тогда это было бы очевидным решением.

Комментарии:

1. Вы пробовали это в других версиях php, например 5.6.25 ?

2. К сожалению, в настоящее время это тоже невозможно. Это производственный сервер, и обновления должны проходить поэтапно. Тестовый сервер никогда не выдает эту проблему, потому что он не получает достаточного трафика (просто я нажимаю на него сто раз, что, кажется, никогда не вызывает проблемы).

3. В PHP версии 5.5.9 есть несколько ошибок, которые вы можете увидеть здесь: php.net/releases/5_5_10.php , просто проверьте, что изменилось

4. Конечно, есть больше обновлений v.5.5.11 .. и т.д. если это возможно, обновите свою версию php до последней стабильной версии.

5. Я предложу это нашему серверному парню, спасибо. Не уверен, почему он выбрал именно этот 5.5. Возможно, детские шаги, поскольку на этом сервере выполняется очень старый код, который я постоянно обновляю.

Ответ №1:

Я уже несколько месяцев наблюдаю аналогичную проблему в той же версии PHP (5.5.9). Также не удалось переключиться на другую версию PHP. Я изо всех сил пытался даже обнаружить это в нашей системе, но, к счастью, прямо сейчас смог отследить и использовать его.

В PHP 5.5.9 функция print_r внутренне использует буферизацию вывода, и в этой версии сообщается об ошибках, касающихся этого print_r и буферизации вывода.

Итак, вот что вам нужно сделать..

Создать скрипт 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 = "Arrayn(n    [TEST] => SOMETHINGn)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 = "123456789n";
$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,8 МБ данных, print_r и, следовательно, любое дополнительное использование буферизации вывода прерывается. Print_r со вторым параметром TRUE возвращает просто ничего. Скорее всего, заканчивается какой-то внутренний или системный буфер, дополнительные символы нигде не могут быть помещены, или уже повторенные символы отбрасываются. Не знаю. Мне не удалось найти корреляцию между пороговым числом и любым параметром конфигурации из phpinfo. Буферизация вывода не имеет значения, установленного в нашей системе.

Мои рекомендации

Так что может быть, что какой-то CURL / WGET использовался и отключался, или обычный браузер использовался и отключался только в начале. Такие имена, как «Ruby», «FeedBurner», звучат для меня как библиотеки или роботы.

a) Если ваш скрипт не слишком сложный, попробуйте избежать буферизации вывода в PHP 5.5.9, а также print_r. var_export в порядке, работает по-другому.

б) При генерации выходных данных замените эхо-сигналы конкатенацией строк, непосредственно для записи в файл. Если вы используете smarty, то это может быть невозможно, потому что smarty часто использует внутреннюю буферизацию вывода.

c) Или создайте список запрещенных агентов, если они являются большинством для этих сбоев.

d) Кстати. если ваш ob_get_level() возвращает 2, это означает, что буферизация вывода в вашей системе по умолчанию включена. Я думаю, вам это не нужно для вашей задачи, отключение этого может даже помочь вам. Стоит попробовать.

Попробуйте запустить те же скрипты в вашей системе и дайте мне знать, каков был максимальный размер эха в вашем случае.

Комментарии:

1. Извините, я не вернулся к этому. Я принял меры предосторожности, чтобы предотвратить пустые выходные данные, поэтому на данный момент вроде как перестал заботиться о том, чтобы тратить на это больше времени. Но ваша информация очень интересна и проницательна, если это так. К сожалению, на данный момент я не могу по-настоящему настроить этот тест! Может быть, если мы вернемся к проблеме, я снова обращусь к этому.

Ответ №2:

Я думаю, что решил аналогичную проблему, добавив строку php_flag output_buffering On в свой .htaccess файл. В моем случае мой PHP-файл содержал сначала HTML-код, затем блок PHP, который вызывал ob_get_contents команду. Иногда результат этого ob_get_contents был пустым. Я не уверен, всегда ли это работает и почему.

Ответ №3:

Вы могли бы обойти эту проблему, используя задание cron, которое перестраивает файл и сохраняет его только при успешном выполнении, а сервер обслуживает только статический файл. Таким образом, вы получите пустую страницу.

Комментарии:

1. У меня это не кэшируется и не отображается в этот момент, по сути, превращая попадание страницы в попадание полной сборки вместо извлечения кэша. Я имею в виду, что это работает… но я уверен, что хотел бы найти основную причину, почему это вообще происходит. Пришлось отложить это из-за других крупных проектов *