Длина содержимого не отправляется, когда сжатие gzip включено в Apache?

13

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

Я общаюсь с PHP из приложения iPhone Objective-C в приложении / 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-Uncompressed-Content-Length - это заголовок, который я добавляю в размер несжатой строки 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 включать фактическую Content-Length для этих больших запросов, чтобы точнее сообщать пользователям об использовании данных?

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

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

Уильям Денисс
источник

Ответы:

14

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

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

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

Philippe
источник
Хороший. Это, вероятно, самый быстрый способ решить эту проблему. Если кому-то нужен более высокий уровень настройки (например, чанки одних запросов, а не других), см. Мой ответ serverfault.com/a/183856/54957 для ручного решения.
Уильям Деннисс
7

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

Мартин Фьордвальд
источник
Спасибо за информацию, вы указали мне правильное направление, и я решил это.
Уильям Деннис
Принятый. Для тех, кто читает этот вопрос, - пожалуйста, прочитайте мой ответ для подробного решения. По сути, вы можете избежать разбиения на части (и, следовательно, нулевой длины содержимого), буферизуя и сжимая ответ вручную.
Уильям Деннис
Это немного сбивает с толку, что принятый ответ - не ответ на первоначальный вопрос, а скорее то, что помогло вам его получить. Возможно, вам следует принять ответ, который вы разместили ниже, чтобы сделать вещи немного более ясными.
Redbmk
@redbmk Справедливо, я просто не хотел показаться неблагодарным. У Филиппа на самом деле есть идеальное простое решение для этого, поэтому я принял его за мой.
Уильям Деннис
5

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

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

Если вы отправляете целые файлы, то это отличный пример в PHP для принудительной установки одного фрагмента (с длиной содержимого): 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 для всего, кроме ответов на приложение / json, которые я теперь кодирую указанным выше способом.

Еще раз спасибо Martin F, вы дали мне искру, мне нужно было решить это :)

Уильям Денисс
источник
1
Кстати, экономия с данными JSON (с сильно повторяющимися ключами) огромна , 77% сокращение в одном случае. Это большое дело за 1 доллар за МБ ...
Уильям Деннис
1
Вы должны, вероятно, просто использовать strlen($replyBody)вместо mb_strlen($replyBody, 'latin1'). Длина содержимого - это количество байтов (не символов), которое дает вам strlen (). Использование mb_strlen () с типом 'latin1' работает, так как символы latin1 всегда 8-битные, но могут возникнуть проблемы с кодировками, которые производят байты, которые не являются допустимыми символами latin1.
orrd