O comprimento do conteúdo não é enviado quando a compactação gzip está ativada no Apache?

13

Eu realmente aprecio alguma ajuda para entender esse comportamento do Apache.

Estou me comunicando com o PHP a partir de um aplicativo iPhone Objective-C no aplicativo / json. A compactação gzip é ativada no servidor e solicitada pelo cliente.

Do meu .htaccess:

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

Para pequenas solicitações, o Apache está configurando o cabeçalho 'Content-Length'. Por exemplo (esses valores são exibidos no Objective-C no cabeçalho):

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 é um cabeçalho que estou adicionando ao tamanho da string JSON descompactada.

Como você pode ver, essa solicitação é muito pequena (217 bytes).

Aqui estão os cabeçalhos de uma solicitação maior (282888 bytes):

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;

Observe que o comprimento do conteúdo não é fornecido.

Minhas perguntas:

  1. Por que o Apache não envia o comprimento do conteúdo para a solicitação maior?
  2. O fato de 'Contend-Encoding = gzip' estar definido significa que a compactação gzip ainda está funcionando em uma solicitação maior, mesmo que eu não consiga verificar a diferença de tamanho?
  3. Existe uma maneira de conseguir que o Apache inclua o comprimento real do conteúdo para essas solicitações maiores para relatar com mais precisão o uso de dados para os usuários?

Este aplicativo pode ser usado em planos de dados caros, portanto, meu desejo de relatar o uso real ao usuário, não 30 a 70% de uso inflado (algumas centenas de KB extras podem não parecer muito - mas esses planos podem custar entre US $ 1 e US $ 10 por MB!).

Desde já, obrigado.

William Denniss
fonte

Respostas:

14

Além da resposta de Martin Fjordvalds:

O Apache usa codificação em pedaços apenas se o tamanho do arquivo compactado for maior que o DeflateBufferSize. Aumentar esse tamanho do buffer impedirá o servidor de usar a codificação em partes também para arquivos maiores, fazendo com que o Comprimento do Conteúdo seja enviado mesmo para dados compactados.

Mais informações estão disponíveis aqui: http://httpd.apache.org/docs/2.2/mod/mod_deflate.html#deflatebuffersize

Philippe
fonte
Agradável. Esta é provavelmente a maneira mais rápida de resolver esse problema. Se alguém precisar de um nível mais alto de personalização (por exemplo, faça algumas solicitações, outras não), consulte minha resposta serverfault.com/a/183856/54957 para obter uma solução manual.
William Denniss
7

Parece que o Apache está codificando em partes, isso significa que ele pode enviar os dados enquanto estão sendo compactados em gzip em vez de aguardar a resposta completa ser compactada. É uma prática bastante padrão, mas não estou familiarizado o suficiente com o Apache para dizer se ele pode ser desativado.

Martin Fjordvald
fonte
Obrigado pela informação, você me apontou na direção certa e eu a resolvi.
William Denniss
Aceitaram. Para quem está lendo esta pergunta, leia minha resposta para obter uma solução detalhada. Basicamente, você pode evitar a fragmentação (e, portanto, o comprimento do conteúdo zero) armazenando em buffer e compactando a resposta manualmente.
William Denniss
É um pouco confuso que a resposta aceita não seja a resposta à pergunta original, mas algo que o ajudou a obtê-la. Talvez você deva aceitar a resposta que postou abaixo para tornar as coisas um pouco mais claras.
redbmk
@redbmk fair point, eu simplesmente não queria parecer ingrato. Philippe realmente tem a solução simples e perfeita para isso, então aceitei a dele sobre a minha.
22713 William Denniss
5

OK, eu consegui resolver isso. Como Martin F corretamente aponta, o Apache está agrupando a resposta para que o tamanho do conteúdo não seja conhecido. Para muitas pessoas, isso é desejável (a página é carregada mais rapidamente). Isso tem o custo de não poder relatar o progresso do download.

Para quem gosta realmente de relatar o progresso do download, se você usa o Apache ou o suporte automático ao gzip do PHP, há pouco o que fazer. A solução é fazê-lo manualmente. É mais fácil do que parece:

Se você estiver enviando arquivos inteiros, este é um ótimo exemplo em PHP para forçar um único pedaço (com o Comprimento do Conteúdo): http://www.php.net/manual/en/function.ob-start.php # 94741

Se você estiver enviando dados gerados, use gzencode para codificar seus dados, como na amostra acima. Um pré-requisito é que todos os seus dados de saída sejam armazenados em uma variável (você pode usar ob_start para ajudar nisso se precisar fazer buffer e obter o conteúdo do buffer).

        // $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;

E pronto!

Outro grande benefício de fazer você mesmo é que você pode definir o nível de compactação. Isso é ótimo para o meu aplicativo móvel, pois eu posso definir o nível de compactação mais alto (para que meus usuários paguem menos pelos dados!) - enquanto o servidor provavelmente usa apenas um nível de compactação médio para uma melhor troca de CPU / tamanho. Os níveis de compactação são algo que acredito que você só poderá alterar se editar o httpd.conf (que na hospedagem compartilhada não posso).

Portanto, eu mantive minha diretiva DEFLATE .htaccess para tudo, exceto minhas respostas application / json, que agora codifico da maneira acima.

Mais uma vez obrigado Martin F, você me deu a faísca que eu precisava para resolver isso :)

William Denniss
fonte
1
Aliás, a economia com dados JSON (com chaves fortemente repetidas) é enorme , redução de 77% em um caso. Isso é um grande negócio em US $ 1 por MB ...
William Denniss
1
Você provavelmente deveria usar apenas em strlen($replyBody)vez de mb_strlen($replyBody, 'latin1'). O comprimento do conteúdo é apenas o número de bytes (não caracteres), que é o que strlen () fornece. O uso de mb_strlen () com o tipo 'latin1' funciona, pois os caracteres latin1 são sempre 8 bits, mas pode haver problemas com codificações que produzem bytes que não são caracteres latin1 válidos.
orrd 8/09/15