Content-Length non envoyé lorsque la compression gzip est activée dans Apache?

13

J'apprécierais vraiment de l'aide pour comprendre ce comportement Apache.

Je communique avec PHP depuis une application iPhone Objective-C dans application / json. La compression Gzip est activée sur le serveur et demandée par le client.

De mon .htaccess:

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

Pour les petites demandes, Apache définit l'en-tête «Content-Length». Par exemple (ces valeurs sont sorties dans Objective-C à partir de l'en-tête):

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 est un en-tête que j'ajoute défini à la taille de la chaîne JSON non compressée.

Comme vous pouvez le voir, cette demande est très petite (217 octets).

Voici les en-têtes d'une demande plus importante (282888 octets):

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;

Notez que Content-Length n'est pas indiqué.

Mes questions:

  1. Pourquoi Apache n'envoie-t-il pas la longueur du contenu pour la plus grande demande?
  2. Le fait que «Contend-Encoding = gzip» soit défini signifie-t-il que la compression gzip fonctionne toujours sur la plus grande demande, même si je ne peux pas vérifier la différence de taille?
  3. Existe-t-il un moyen d'obtenir qu'Apache inclue la longueur de contenu réelle pour ces demandes plus importantes afin de rapporter plus précisément l'utilisation des données aux utilisateurs?

Cette application peut être utilisée sur des plans de données qui sont chers, d'où mon désir de signaler l'utilisation réelle à l'utilisateur, pas une utilisation gonflée de 30 à 70% (quelques centaines de Ko supplémentaires peuvent ne pas sembler beaucoup - mais ces plans peuvent coûter entre 1 $ et 10 $ par Mo!).

Merci d'avance.

William Denniss
la source

Réponses:

14

Ajout à la réponse de Martin Fjordvalds:

Apache utilise un codage fragmenté uniquement si la taille du fichier compressé est supérieure à DeflateBufferSize. L'augmentation de cette taille de tampon empêchera donc le serveur d'utiliser un codage en morceaux également pour les fichiers plus volumineux, provoquant l'envoi de Content-Length même pour les données zippées.

Plus d'informations sont disponibles ici: http://httpd.apache.org/docs/2.2/mod/mod_deflate.html#deflatebuffersize

Philippe
la source
Joli. C'est probablement le moyen le plus rapide pour résoudre ce problème. Si quelqu'un a besoin d'un niveau de personnalisation plus élevé (par exemple, fragmenter certaines demandes, pas d'autres), consultez ma réponse serverfault.com/a/183856/54957 pour une solution manuelle.
William Denniss
7

On dirait qu'Apache fait un codage en morceaux, cela signifie qu'il peut envoyer les données pendant qu'il est compressé au lieu d'attendre que la réponse complète soit compressée. C'est une pratique assez standard, je ne connais pas suffisamment Apache pour dire s'il peut être désactivé, cependant.

Martin Fjordvald
la source
Merci pour l'info, tu m'as pointé dans la bonne direction, et je l'ai résolu.
William Denniss
Accepté. Si vous lisez cette question, veuillez lire ma réponse pour une solution détaillée. Fondamentalement, vous pouvez éviter la segmentation (et donc la longueur de contenu nulle) en tamponnant et en compressant la réponse manuellement.
William Denniss
Il est un peu déroutant que la réponse acceptée ne soit pas la réponse à la question d'origine, mais plutôt quelque chose qui vous a aidé à l'obtenir. Vous devriez peut-être accepter la réponse que vous avez publiée ci-dessous pour clarifier les choses.
redbmk
@redbmk juste point, je ne voulais tout simplement pas paraître ingrat. Philippe a en fait la solution simple parfaite pour cela, j'ai donc accepté le sien.
William Denniss
5

OK, j'ai réussi à résoudre ce problème. Comme Martin F le fait remarquer à juste titre, Apache segmente la réponse de sorte que la taille du contenu n'est pas connue. Pour de nombreuses personnes, cela est souhaitable (la page se charge plus rapidement). Cela a pour conséquence de ne pas pouvoir signaler la progression du téléchargement.

Pour ceux comme moi qui veulent vraiment signaler la progression du téléchargement, si vous utilisez Apache ou le support gzip automatique de PHP, vous ne pouvez pas faire grand-chose. La solution est de le faire manuellement. C'est plus simple qu'il n'y paraît:

Si vous envoyez des fichiers entiers, alors c'est un excellent exemple en PHP pour forcer un seul morceau (avec la Content-Length): http://www.php.net/manual/en/function.ob-start.php # 94741

Si vous envoyez des données générées, utilisez gzencode pour encoder vos données, comme dans l'exemple ci-dessus. Une condition préalable est que toutes vos données de sortie soient stockées dans une variable (vous pouvez utiliser ob_start pour vous aider si vous avez besoin de tamponner, puis d'obtenir le contenu du tampon).

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

Et le tour est joué!

Un autre grand avantage de le faire vous-même est que vous pouvez définir le niveau de compression. C'est génial pour mon application mobile, car je peux définir le niveau de compression le plus élevé (donc mes utilisateurs paient moins pour les données!) - tandis que le serveur n'utilise probablement qu'un niveau de compression moyen pour un meilleur compromis CPU / taille. Les niveaux de compression sont quelque chose que je crois que vous ne pouvez changer que si vous pouvez modifier le httpd.conf (que sur l'hébergement partagé, je ne peux pas).

J'ai donc conservé ma directive DEFLATE .htaccess pour tout sauf mes réponses application / json que j'encode maintenant de la manière ci-dessus.

Merci encore Martin F, vous m'avez donné l'étincelle dont j'avais besoin pour résoudre ce problème :)

William Denniss
la source
1
Soit dit en passant, les économies réalisées avec les données JSON (avec des clés fortement répétées) sont énormes , soit une réduction de 77% dans un cas. C'est une grosse affaire à 1 $ par Mo ...
William Denniss
1
Vous devriez probablement simplement utiliser strlen($replyBody)au lieu de mb_strlen($replyBody, 'latin1'). La longueur du contenu est juste le nombre d'octets (pas de caractères), ce que strlen () vous donne. L'utilisation de mb_strlen () avec le type de travail 'latin1' puisque les caractères latin1 sont toujours à 8 bits, mais cela peut avoir des problèmes avec les encodages qui produisent des octets qui ne sont pas des caractères latin1 valides.
2015 à