Content-Length não enviado quando a compactação gzip é ativada no Apache?

12

Eu realmente gostaria de receber ajuda para entender esse comportamento do Apache.

Estou me comunicando com o PHP 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 pedidos pequenos, o Apache está configurando o cabeçalho 'Content-Length'. Por exemplo (esses valores são exibidos em 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;

Comprimento do conteúdo X-Uncompressed é 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 Content-Length para a solicitação maior?
  2. O fato de que 'Contend-Encoding = gzip' está definido significa que a compactação gzip ainda está funcionando na 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 do conteúdo real 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 que são caros, portanto, meu desejo de relatar o uso real para o usuário, não 30-70% de uso inflacionado (algumas centenas de KB extra podem não parecer muito - mas esses planos podem custo entre US $ 1 e US $ 10 por MB!).

Obrigado antecipadamente.

    
por William Denniss 23.09.2010 / 04:24

3 respostas

12

Além da resposta de Martin Fjordvalds:

O Apache usa codificação em partes somente se o tamanho do arquivo compactado for maior que o DeflateBufferSize. Aumentar esse tamanho de buffer impedirá, portanto, que o servidor use codificação em partes também para arquivos maiores, fazendo com que o Content-Length seja enviado mesmo para dados compactados.

Mais informações estão disponíveis aqui: link

    
por 30.05.2012 / 14:28
6

Parece que o Apache está fazendo codificação em partes, isso significa que ele pode enviar os dados como estão sendo compactados em vez de esperar que a resposta completa seja gzipada. É bastante prática comum, embora eu não esteja familiarizado o suficiente com o Apache para dizer se ele pode ser desativado.

    
por 23.09.2010 / 04:35
4

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

Para aqueles que, como eu, querem realmente relatar o progresso do download, se você usa o suporte automático ao gzip do Apache ou do PHP, há pouco que você possa fazer. A solução é fazer isso manualmente. É mais fácil do que parece:

Se você está enviando arquivos inteiros, então este é um ótimo exemplo em PHP para forçar um único pedaço (com o Content-Length): link

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 ajudá-lo se precisar armazenar em buffer e, em seguida, 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 voila!

Outra grande vantagem de fazer isso sozinho é que você pode definir o nível de compactação. Isso é ótimo para meu aplicativo móvel, já que posso definir o nível mais alto de compactação (para que meus usuários paguem menos pelos dados!) - enquanto o servidor provavelmente usa apenas um nível médio de compactação para obter um melhor compromisso CPU / tamanho. Os níveis de compressão são algo que eu acredito que você só pode mudar se você pode editar o httpd.conf (que em hospedagem compartilhada, eu não posso).

Portanto, mantive minha diretiva DEFLATE .htaccess para tudo, mas meu aplicativo / json responde, que agora codifico da maneira acima.

Obrigado novamente Martin F, você me deu a faísca que eu precisava para resolver isso:)

    
por 23.09.2010 / 06:26