Content-Length non inviato quando la compressione gzip è abilitata in Apache?


13

Gradirei davvero un po 'di aiuto per comprendere questo comportamento di Apache.

Sto comunicando a PHP da un'app iPhone Objective-C in application / json. La compressione gzip è abilitata sul server e richiesta dal client.

Dal mio .htaccess:

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

Per piccole richieste, Apache sta impostando l'intestazione "Content-Length". Ad esempio (questi valori vengono emessi in Objective-C dall'intestazione):

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 è un'intestazione che sto aggiungendo impostata sulla dimensione della stringa JSON non compressa.

Come puoi vedere, questa richiesta è molto piccola (217 byte).

Ecco le intestazioni di una richiesta più ampia (282888 byte):

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;

Si noti che la lunghezza del contenuto non viene fornita.

Le mie domande:

  1. Perché Apache non invia Content-Length per la richiesta più grande?
  2. Il fatto che 'Contend-Encoding = gzip' sia impostato significa che la compressione gzip funziona ancora sulla richiesta più grande, anche se non riesco a verificare la differenza di dimensioni?
  3. Esiste un modo per convincere Apache a includere l'effettiva lunghezza del contenuto per queste richieste più grandi per segnalare in modo più accurato l'utilizzo dei dati agli utenti?

Questa app può essere utilizzata su piani dati che sono costosi, quindi il mio desiderio di segnalare all'utente l'effettivo utilizzo, non un uso gonfiato del 30-70% (alcune centinaia di KB in più potrebbero non sembrare molto - ma questi piani possono costare tra $ 1 e $ 10 per MB!).

Grazie in anticipo.

Risposte:


14

Aggiunta alla risposta di Martin Fjordvalds:

Apache utilizza la codifica in blocchi solo se la dimensione del file compresso è maggiore di DeflateBufferSize. L'aumento di questa dimensione del buffer impedirà quindi al server di utilizzare la codifica in blocchi anche per file più grandi, causando l'invio della lunghezza del contenuto anche per i dati compressi.

Maggiori informazioni sono disponibili qui: http://httpd.apache.org/docs/2.2/mod/mod_deflate.html#deflatebuffersize


Ben fatto. Questo è probabilmente il modo più veloce per risolvere questo problema. Se qualcuno ha bisogno di un livello più alto di personalizzazione (ad esempio, chunk alcune richieste, non altre), vedere la mia risposta serverfault.com/a/183856/54957 per una soluzione manuale.
William Denniss,

7

Sembra che Apache stia eseguendo la codifica in blocchi, ciò significa che può inviare i dati mentre vengono compressi con il gzip anziché attendere che venga fornita la gzip con la risposta completa. È una pratica abbastanza standard, non ho abbastanza familiarità con Apache per dire se può essere disabilitato, comunque.


Grazie per le informazioni, mi hai indicato la giusta direzione e l'ho risolto.
William Denniss,

Accettato. Per chiunque legga questa domanda, leggi la mia risposta per una soluzione dettagliata. Fondamentalmente, puoi evitare blocchi (e quindi la lunghezza del contenuto zero) bufferizzando e comprimendo la risposta manualmente.
William Denniss,

È un po 'confuso che la risposta accettata non sia la risposta alla domanda originale, ma piuttosto qualcosa che ti abbia aiutato a ottenerla. Forse dovresti accettare la risposta che hai postato di seguito per rendere le cose un po 'più chiare.
Redbmk

@redbmk punto giusto, non volevo sembrare ingrato. Philippe in realtà ha la soluzione semplice perfetta per questo, quindi ho accettato il suo rispetto al mio.
William Denniss,

5

OK, sono riuscito a risolverlo. Come sottolinea correttamente Martin F, Apache sta tagliando la risposta in modo che la dimensione del contenuto non sia nota. Per molte persone questo è desiderabile (la pagina si carica più velocemente). Questo ha il costo di non essere in grado di riportare l'avanzamento del download.

Per quelli come me che vogliono davvero segnalare l'avanzamento del download, se usi Apache o il supporto gzip automatico di PHP, c'è poco che puoi fare. La soluzione è farlo manualmente. È più facile di quanto sembri:

Se stai inviando interi file, questo è un ottimo esempio in PHP per forzare un singolo pezzo (con Content-Length): http://www.php.net/manual/en/function.ob-start.php # 94.741

Se stai inviando dati generati, usa gzencode per codificare i tuoi dati, come nell'esempio sopra. Un prerequisito è che tutti i tuoi dati di output siano archiviati in una variabile (puoi usare ob_start per aiutarti se hai bisogno di buffer, quindi ottenere il contenuto del 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 voilà!

Un altro grande vantaggio di farlo da soli è che puoi impostare il livello di compressione. Questo è ottimo per la mia applicazione mobile, poiché posso impostare il livello di compressione più alto (quindi i miei utenti pagano meno per i dati!) - mentre il server probabilmente utilizza solo un livello di compressione medio per un migliore compromesso CPU / dimensione. I livelli di compressione sono qualcosa che credo tu possa cambiare solo se riesci a modificare httpd.conf (che sull'hosting condiviso non posso).

Quindi ho mantenuto la mia direttiva DEFLATE .htaccess per tutto tranne le mie risposte application / json che ora codifico nel modo sopra.

Grazie ancora Martin F, mi hai dato la scintilla di cui avevo bisogno per risolvere questo :)


1
Per inciso, i risparmi con i dati JSON (con tasti fortemente ripetuti) sono enormi , con una riduzione del 77% in un caso. È un grosso problema a $ 1 per MB ...
William Denniss,

1
Probabilmente dovresti semplicemente usare strlen($replyBody)invece di mb_strlen($replyBody, 'latin1'). La lunghezza del contenuto è solo il numero di byte (non caratteri), che è ciò che ti dà strlen (). L'uso di mb_strlen () con una sorta di 'latin1' funziona poiché i caratteri latin1 sono sempre 8 bit, ma potrebbero avere problemi con le codifiche che producono byte che non sono validi caratteri latin1.
or
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.