Content-Length wird nicht gesendet, wenn die gzip-Komprimierung in Apache aktiviert ist?

13

Ich würde mich sehr über Hilfe beim Verständnis dieses Apache-Verhaltens freuen.

Ich kommuniziere mit PHP über eine iPhone Objective-C-App in application / json. Die Gzip-Komprimierung ist auf dem Server aktiviert und wird vom Client angefordert.

Aus meiner .htaccess:

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

Für kleine Anfragen setzt Apache den 'Content-Length'-Header. Zum Beispiel (diese Werte werden in Objective-C aus dem Header ausgegeben):

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 ist ein Header, den ich zur Größe der unkomprimierten JSON-Zeichenfolge hinzufüge.

Wie Sie sehen, ist diese Anforderung sehr klein (217 Byte).

Hier sind die Header einer größeren Anfrage (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;

Beachten Sie, dass die Inhaltslänge nicht angegeben ist.

Meine Fragen:

  1. Warum sendet Apache die Content-Length für die größere Anfrage nicht?
  2. Bedeutet die Tatsache, dass 'Contend-Encoding = gzip' gesetzt ist, dass die gzip-Komprimierung immer noch bei der größeren Anforderung funktioniert, obwohl ich den Größenunterschied nicht überprüfen kann?
  3. Gibt es eine Möglichkeit, Apache dazu zu bringen, die tatsächliche Inhaltslänge für diese größeren Anforderungen anzugeben, um den Benutzern die Datennutzung genauer zu melden?

Diese App kann für Datentarife verwendet werden, die teuer sind. Daher möchte ich dem Benutzer den tatsächlichen Verbrauch melden, nicht 30-70% überhöhten Verbrauch (ein paar hundert zusätzliche KB klingen möglicherweise nicht nach viel - aber diese Tarife können zwischen 1 USD kosten und $ 10 pro MB!).

Danke im Voraus.

William Denniss
quelle

Antworten:

14

Ergänzung zu Martin Fjordvalds Antwort:

Apache verwendet Chunk-Codierung nur, wenn die komprimierte Dateigröße größer als DeflateBufferSize ist. Das Erhöhen dieser Puffergröße verhindert daher, dass der Server auch für größere Dateien eine Chunk-Codierung verwendet, wodurch die Inhaltslänge selbst für komprimierte Daten gesendet wird.

Weitere Informationen finden Sie hier: http://httpd.apache.org/docs/2.2/mod/mod_deflate.html#deflatebuffersize

Philippe
quelle
Schön. Dies ist wahrscheinlich der schnellste Weg, um dieses Problem zu lösen. Wenn jemand eine höhere Anpassungsstufe benötigt (z. B. einige Anfragen teilen , andere nicht), finden Sie in meiner Antwort serverfault.com/a/183856/54957 eine manuelle Lösung.
William Denniss
7

Klingt so, als ob Apache eine Chunk-Codierung ausführt. Dies bedeutet, dass die Daten während der Gzip-Verarbeitung gesendet werden können, anstatt auf die vollständige Gzip-Antwort zu warten. Es ist ziemlich üblich, aber ich kenne Apache nicht genug, um zu sagen, ob es deaktiviert werden kann.

Martin Fjordvald
quelle
Danke für die Info, du hast mich in die richtige Richtung gelenkt und ich habe es gelöst.
William Denniss
Akzeptiert. Wenn Sie diese Frage lesen, lesen Sie bitte meine Antwort für eine detaillierte Lösung. Grundsätzlich können Sie Chunking (und damit die Länge von Null) vermeiden, indem Sie die Antwort manuell puffern und komprimieren.
William Denniss
Es ist ein wenig verwirrend, dass die akzeptierte Antwort nicht die Antwort auf die ursprüngliche Frage ist, sondern etwas, das Ihnen geholfen hat, sie zu bekommen. Vielleicht sollten Sie die Antwort, die Sie unten gepostet haben, akzeptieren, um die Dinge ein wenig klarer zu machen.
Redbmk
@redbmk fair point, ich wollte einfach nicht undankbar wirken. Philippe hat tatsächlich die perfekte einfache Lösung dafür, also habe ich seine über meine akzeptiert.
William Denniss
5

OK, ich habe es geschafft, das zu lösen. Wie Martin F richtig hervorhebt, teilt Apache die Antwort so auf, dass die Inhaltsgröße nicht bekannt ist. Für viele Menschen ist dies wünschenswert (Seite wird schneller geladen). Dies hat den Nachteil, dass der Download-Fortschritt nicht gemeldet werden kann.

Für diejenigen wie mich, die wirklich über den Download-Fortschritt berichten möchten, gibt es wenig zu tun, wenn Sie die automatische gzip-Unterstützung von Apache oder PHP verwenden. Die Lösung besteht darin, es manuell zu tun. Es ist einfacher als es klingt:

Wenn Sie ganze Dateien senden, ist dies ein großartiges Beispiel in PHP, um einen einzelnen Block (mit der Inhaltslänge) zu erzwingen: http://www.php.net/manual/en/function.ob-start.php # 94741

Wenn Sie generierte Daten senden, verwenden Sie gzencode, um Ihre Daten zu verschlüsseln, wie im obigen Beispiel. Voraussetzung ist, dass alle Ihre Ausgabedaten in einer Variablen gespeichert sind (Sie können ob_start verwenden, um dies zu unterstützen, wenn Sie puffern müssen, und dann den Inhalt des Puffers abrufen).

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

Und voila!

Ein weiterer großer Vorteil ist, dass Sie die Komprimierungsstufe einstellen können. Dies ist ideal für meine mobile Anwendung, da ich die höchste Komprimierungsstufe einstellen kann (sodass meine Benutzer weniger für Daten bezahlen!) - während der Server wahrscheinlich nur eine mittlere Komprimierungsstufe verwendet, um einen besseren Kompromiss zwischen CPU und Größe zu erzielen. Komprimierungsstufen können Sie meines Erachtens nur ändern, wenn Sie die Datei httpd.conf bearbeiten können (was bei Shared Hosting nicht möglich ist).

Daher habe ich meine DEFLATE .htaccess-Direktive für alles außer meinen application / json-Antworten beibehalten, die ich jetzt auf die oben beschriebene Weise codiere.

Nochmals vielen Dank, Martin F., du hast mir den Funken gegeben, den ich brauchte, um das zu lösen :)

William Denniss
quelle
1
Im Übrigen sind die Einsparungen bei JSON-Daten (mit stark wiederholten Schlüsseln) enorm , in einem Fall um 77%. Das ist eine große Sache bei 1 $ pro MB ...
William Denniss
1
Sie sollten wahrscheinlich nur strlen($replyBody)anstelle von verwenden mb_strlen($replyBody, 'latin1'). Die Inhaltslänge ist nur die Anzahl der Bytes (nicht der Zeichen), die Sie mit strlen () erhalten. Die Verwendung von mb_strlen () mit 'latin1' funktioniert, da latin1-Zeichen immer 8 Bit lang sind. Es kann jedoch Probleme mit Codierungen geben, die Bytes erzeugen, die keine gültigen latin1-Zeichen sind.
8.