Назад | Перейти на главную страницу

Content-Length не отправляется при включенном сжатии gzip в Apache?

Я был бы очень признателен за помощь в понимании этого поведения Apache.

Я общаюсь с PHP из приложения iPhone Objective-C в application / json. Сжатие Gzip включено на сервере и запрашивается клиентом.

Из моего .htaccess:

AddOutputFilterByType DEFLATE text/html text/plain text/xml application/x-httpd-php application/json

Для небольших запросов Apache устанавливает заголовок Content-Length. Например (эти значения выводятся в Objective-C из заголовка):

Connection = "Keep-Alive";
"Content-Encoding" = gzip;
"Content-Length" = 185;     <-------------
"Content-Type" = "application/json";
Date = "Wed, 22 Sep 2010 12:20:27 GMT";
"Keep-Alive" = "timeout=3, max=149";
Server = Apache;
Vary = "Accept-Encoding";
"X-Powered-By" = "PHP/5.2.13";
"X-Uncompressed-Content-Length" = 217;

X-несжатый-контент-длина - добавляемый заголовок, равный размеру несжатой строки JSON.

Как видите, этот запрос очень маленький (217 байт).

Вот заголовки из большего запроса (282888 байт):

Connection = "Keep-Alive";
"Content-Encoding" = gzip;
"Content-Type" = "application/json";
Date = "Wed, 22 Sep 2010 12:20:29 GMT";
"Keep-Alive" = "timeout=3, max=148";
Server = Apache;
"Transfer-Encoding" = Identity;
Vary = "Accept-Encoding";
"X-Powered-By" = "PHP/5.2.13";
"X-Uncompressed-Content-Length" = 282888;

Обратите внимание, что Content-Length не указывается.

Мои вопросы:

  1. Почему Apache не отправляет Content-Length для большего запроса?
  2. Означает ли тот факт, что установлен параметр Contend-Encoding = gzip, сжатие gzip по-прежнему работает с большим запросом, хотя я не могу проверить разницу в размерах?
  3. Есть ли способ заставить Apache включать фактическую длину содержимого для этих больших запросов, чтобы более точно сообщать пользователям об использовании данных?

Это приложение можно использовать в дорогих тарифных планах, поэтому я хочу сообщать пользователю о фактическом использовании, а не о завышенном использовании на 30-70% (несколько сотен дополнительных КБ могут показаться не такими уж большими, но эти планы могут стоить от 1 доллара США. и 10 долларов за Мб!).

Заранее спасибо.

Дополнение к ответу Мартина Фьордвальдса:

Apache использует фрагментированное кодирование только в том случае, если размер сжатого файла больше, чем DeflateBufferSize. Поэтому увеличение этого размера буфера предотвратит использование сервером фрагментированного кодирования также для больших файлов, в результате чего Content-Length будет отправляться даже для заархивированных данных.

Более подробная информация доступна здесь: http://httpd.apache.org/docs/2.2/mod/mod_deflate.html#deflatebuffersize

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

Хорошо, мне удалось это решить. Как правильно указывает Мартин Ф, Apache разбивает ответ на части, поэтому размер содержимого неизвестен. Для многих это желательно (страница загружается быстрее). Это происходит за счет невозможности сообщить о ходе загрузки.

Для тех, кто, как я, действительно хочет сообщать о ходе загрузки, если вы используете Apache или автоматическую поддержку gzip PHP, вы мало что можете сделать. Решение - сделать это вручную. Это проще, чем кажется:

Если вы отправляете файлы целиком, то это отличный пример в PHP принудительного использования одного фрагмента (с Content-Length): http://www.php.net/manual/en/function.ob-start.php#94741

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

        // $replyBody is the entire contents of your reply

        header("Content-Type: application/json");  // or whatever yours is

        // checks if gzip is supported by client
        $pack = true;
        if(empty($_SERVER["HTTP_ACCEPT_ENCODING"]) || strpos($_SERVER["HTTP_ACCEPT_ENCODING"], 'gzip') === false)
        {
            $pack = false;
        }

        // if supported, gzips data
        if($pack) {
            header("Content-Encoding: gzip");
            $replyBody = gzencode($replyBody, 9, FORCE_GZIP);
        }

        // compressed or not, sets the Content-Length           
        header("Content-Length: " . mb_strlen($replyBody, 'latin1'));

        // outputs reply & exits
        echo $replyBody;
        exit;

И вуаля!

Еще одним большим преимуществом самостоятельного выполнения является то, что вы можете установить уровень сжатия. Это отлично подходит для моего мобильного приложения, так как я могу установить самый высокий уровень сжатия (так что мои пользователи платят меньше за данные!) - тогда как сервер, вероятно, использует только средний уровень сжатия для лучшего компромисса между процессором и размером. Я считаю, что уровни сжатия можно изменить только в том случае, если вы можете редактировать httpd.conf (что на виртуальном хостинге я не могу).

Итак, я сохранил свою директиву DEFLATE .htaccess для всего, кроме ответов application / json, которые я теперь кодирую указанным выше способом.

Еще раз спасибо, Мартин Ф, ты дал мне искру, которая мне нужна, чтобы решить эту проблему :)