Как определить длину содержимого gzip-файла?

Прямо сейчас я пытаюсь обслуживать файлы CSS и JS с сервера, который не позволяет мне включить mod_gzip или mod_deflate. Поэтому я написал небольшой PHP-скрипт для сжатия с помощью GZIP и возврата пользователю.

Пример кода:

$filename = "style.css";

if (!file_exists($filename) || !($info = stat($filename))) {
  header("HTTP/1.1 404 Not Found");
  die();
}

header("Date: ".gmdate("D, j M Y H:i:s e", time()));
header("Cache-Control: max-age=2592000");
header("Last-Modified: ".gmdate("D, j M Y H:i:s e", $info['mtime']));
header("Etag: ".sprintf("\"%x-%x-%x\"", $info['ino'], $info['size'], $info['mtime']));
header("Accept-Ranges: bytes");
header("Cache-Control: Expires ".gmdate("D, j M Y H:i:s e", $info['mtime']+2592000));
header("Content-Type: text/html");

ob_start("ob_gzhandler");
echo file_get_contents($filename);
ob_end_flush();

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

header("Content-Length: ".$info["size"]);

Но если я это сделаю, браузер зависнет, пытаясь дождаться дополнительных данных. Есть ли способ рассчитать общий размер? Или я должен игнорировать эту директиву заголовка.

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

Редактировать: благодаря SoapBox я заменил конец кода на это:

header("Content-Encoding: gzip");
$compressed = gzencode(file_get_contents($filename), 5);
header("Content-Length: ".strlen($compressed));
die($compressed);

Это отлично работает для длины контента! Но я все еще заставляю Firefox загружать файл, а не отображать его. :(

Редактировать еще раз. Вот измененный код конца кода, любезно предоставленный Cletus.

// Start buffered output
ob_start();
// Check for gzip capability
if (stripos($_SERVER['HTTP_ACCEPT_ENCODING'], "gzip") !== false) {
  ob_start("ob_gzhandler");
  echo file_get_contents($filename);
  ob_end_flush();
} else
  echo file_get_contents($filename);

// Write the content length
header('Content-Length: '.ob_get_length());
ob_end_flush();

Я собираюсь начать новый вопрос, чтобы выяснить, почему Firefox продолжает пытаться загрузить файл.


person St. John Johnson    schedule 03.05.2009    source источник
comment
Некоторые из заголовков, упомянутых в ответах, содержат несколько ошибок; Двойной Cache-Control, строка с Cache-Control: Expires.... должна быть Expires: ... И Etag должна быть ETag.   -  person    schedule 24.01.2011


Ответы (3)


Проблема здесь в том, что для того, чтобы узнать длину контента, вам нужно знать, поддерживает ли клиент кодировку gzip, и вы делегировали это решение с помощью ob_gzhandler. Из заголовков HTTP:

ob_start();
ob_start('ob_gzhandler');

  ... output the page content...

ob_end_flush();  // The ob_gzhandler one

header('Content-Length: '.ob_get_length());

ob_end_flush();  // The main one

Полная версия:

$filename = "style.css";

if (!file_exists($filename) || !($info = stat($filename))) {
  header("HTTP/1.1 404 Not Found");
  die();
}

header("Date: ".gmdate("D, j M Y H:i:s e", time()));
header("Cache-Control: max-age=2592000");
header("Last-Modified: ".gmdate("D, j M Y H:i:s e", $info['mtime']));
header("ETag: ".sprintf("\"%x-%x-%x\"", $info['ino'], $info['size'], $info['mtime']));
header("Accept-Ranges: bytes");
header("Expires: ".gmdate("D, j M Y H:i:s e", $info['mtime']+2592000));
header("Content-Type: text/css"); // note: this was text/html for some reason?

ob_start();
ob_start("ob_gzhandler");
echo file_get_contents($filename);
ob_end_flush();
header('Content-Length: '.ob_get_length());
ob_end_flush();

Это намного лучше, чем решать проблему с кодировкой gzip самостоятельно.

person cletus    schedule 03.05.2009

Вам нужно сначала выполнить весь gzip и измерить результат (либо удерживая содержимое в памяти, либо записывая его на диск при сжатии, а затем указав gzip-файл), затем запишите заголовок Content-Length и затем отправьте файл содержание.

Или используйте кодирование групповой передачи.

person Jeremy Huiskamp    schedule 03.05.2009

Чтобы решить вашу проблему с Firefox, я думаю, вам нужно включить header( "Content-Encoding: gzip" );, чтобы браузер знал, что нужно распаковывать содержимое.

Что касается длины содержимого, вы можете попробовать просто оставить это значение отключенным или попытаться найти способ использовать «Transfer-Encoding: chunked» (вы не можете просто отправить этот заголовок, вам нужно отформатировать данные специально для него ). Возможно, ob_end_flush автоматически включает фрагментацию.

Я рекомендую вам получить wireshark и захватить то, что отправляет ваш php-скрипт, и сравнить его с правильно работающим сервером, чтобы увидеть, какие заголовки и т. д. отсутствуют.

person SoapBox    schedule 03.05.2009
comment
На самом деле это то, что делает ob_start(ob_gzhandler), если клиент это поддерживает. - person cletus; 03.05.2009