Друг попросил меня помочь выяснить, почему переписывание какого-то очень-очень старого программного обеспечения вызывает у него такие головные боли. Обычно я ничего не делаю для друзей, но это показалось достаточно простым. Проблема оказалась настолько наивной, что пришлось поделиться.
Компанию, в которой он работает, взломали, и причина была до боли очевидна. Они все еще использовали программное обеспечение, которое работало только на PHP 4.0. Да, мы говорим о программном обеспечении более чем 20-летней давности.
Как правило, он может обрабатывать современный PHP-код, поэтому мне было очень любопытно, что заставило его повесить трубку, и это было втрое.
- Старое программное обеспечение, которое у них было, которое анализировало ссылки на страницы сайта, давало сбой при его переписывании, даже не доходя до разбора разметки, что странно, поскольку оно более современное и использует XMLDocument, а его выходная разметка была действительной!
- Ошибки появлялись в его журнале ошибок, но не выводились, несмотря на то, что были включены «все» отчеты об ошибках.
- Несколько страниц выдавали пустое белое ничего, нулевой контент в ответе.
Все это было вызвано рядом проблем с кодированием, все из которых были связаны с тем, как обрабатывалось сжатие/упаковка вывода 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 (все четыре тега по-прежнему служат уникальным и разным целям) и так далее. Действительно кажется, что в наши дни все превращают «когда уместно» или «предпочтительно» в «ВСЕГДА», когда это не так.
Надеюсь, кому-то это покажется если не полезным, то хотя бы интересным.