Друг попросил меня помочь выяснить, почему переписывание какого-то очень-очень старого программного обеспечения вызывает у него такие головные боли. Обычно я ничего не делаю для друзей, но это показалось достаточно простым. Проблема оказалась настолько наивной, что пришлось поделиться.

Компанию, в которой он работает, взломали, и причина была до боли очевидна. Они все еще использовали программное обеспечение, которое работало только на PHP 4.0. Да, мы говорим о программном обеспечении более чем 20-летней давности.

Как правило, он может обрабатывать современный PHP-код, поэтому мне было очень любопытно, что заставило его повесить трубку, и это было втрое.

  1. Старое программное обеспечение, которое у них было, которое анализировало ссылки на страницы сайта, давало сбой при его переписывании, даже не доходя до разбора разметки, что странно, поскольку оно более современное и использует XMLDocument, а его выходная разметка была действительной!
  2. Ошибки появлялись в его журнале ошибок, но не выводились, несмотря на то, что были включены «все» отчеты об ошибках.
  3. Несколько страниц выдавали пустое белое ничего, нулевой контент в ответе.

Все это было вызвано рядом проблем с кодированием, все из которых были связаны с тем, как обрабатывалось сжатие/упаковка вывода HTML.

PHP.INI Zlib удобен, но не надежен

Одной из первых вещей, которые он сделал в перекодировании, было вытащить ob_start() с помощью старинного метода ручного сжатия. Раньше gz_handler даже был опцией. Вместо этого он просто включил сжатие в PHP.INI с помощью:

zlib.output_compression = "1"

На самом деле это то, что приводило к сбою стороннего программного обеспечения для анализа сайтов. Он не поддерживает сжатую разметку, и, по-видимому, — по крайней мере, на его хостинге — процедура сжатия вывода не проверяет заголовки, чтобы узнать, поддерживается ли вообще сжатие!

Hurr-Durrz о части PHP/zlib.

Буферизация вывода

Некоторые страницы, которые давали сбой, пытались установить файлы cookie или заголовки после начала вывода. Если вы что-то знаете о PHP — или веб-разработке в целом — вы знаете, что не можете этого сделать… так почему в старой программе работало?

Поскольку он выполнял ob_start. Технически zlib PHP.ini должен был обрабатывать преждевременные выходы и правильно сжимать, но нет. Это действовало так, как будто вывод был пустым по ошибке. Отсюда и ошибки в логах и пустые страницы.

Частичное исправление могло бы использовать ob_start, но ошибки все равно могли завершаться без сброса.

Решение?

Вернитесь к выполнению этого вручную, но с более современным подходом. Поскольку это программа с одним входом, легко подключить сжатие с самого начала. Отключите сжатие php.ini, вернитесь к ob_start, но вместо этого используйте gzhandler, и используйте функцию register_shutdown_function для настройки header() и сброса.

foreach (['gzip', 'x-gzip', 'x-compress'] as $type) {
  if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], $type) !== false) {
    define('CONTENT_ENCODING', $type);
    break;
  }
}
ob_start(defined('CONTENT_ENCODING') ? 'ob_gzhandler' : null);
ob_implicit_flush(0);
register_shutdown_function(function() { 
  if (defined('CONTENT_ENCODING')) header(
    'Content-Encoding: ' . CONTENT_ENCODING
  );
  ob_end_flush();
});

Определите, поддерживает ли браузер сжатие gzip. Если он начинается с ob_gzhandler, если не все еще буферизируется, так что вы можете использовать header() или устанавливать cookies() до посинения. Отключите неявную очистку, чтобы мы могли буферизовать весь вывод, а не только один фрагмент за раз. Зарегистрируйте функцию завершения работы, чтобы установить заголовок, если это необходимо, затем выполните ob_end_flush для отправки вывода содержимого.

Не нужно помнить о сбросе в конце или слепо надеяться, что PHP сделает это за вас.

Заключение

Это было простое исправление, и оно позволило моему другу продолжить отладку своей перезаписи. Я всегда использовал код, похожий на этот, почти с того дня, когда я начал использовать PHP несколько десятилетий назад, просто потому, что возможность использовать заголовки и файлы cookie везде, где это необходимо, значительно упрощает использование языка.

Это не обязательно красиво, но выполняет работу лучше, чем большинство альтернатив.

Я немного встревожен тем, что по какой-то причине настройка php.ini просто вслепую отправляет его в заархивированном виде без проверки значения HTTP_ACCEPT_ENCODING. Не знаю, нормально это или нет, но если так, то нехорошо.

В любом случае, легко начать говорить «rawrz, я могу это модернизировать!» и начать рвать вещи, не задумываясь о том, почему решение было принято в первую очередь. Особенно, когда в документации говорится что-то вроде «Вместо этого следует использовать параметр PHP.INI».

Я слишком часто вижу, как люди слепо применяют ко всему совет, в котором говорится «когда это уместно». Посмотрите, через какие сумасшедшие ухищрения люди прыгают, чтобы избежать использования таблиц «потому что», или неверное толкование STRONG и EM вместо B и I (все четыре тега по-прежнему служат уникальным и разным целям) и так далее. Действительно кажется, что в наши дни все превращают «когда уместно» или «предпочтительно» в «ВСЕГДА», когда это не так.

Надеюсь, кому-то это покажется если не полезным, то хотя бы интересным.