¿La longitud del contenido no se envía cuando la compresión gzip está habilitada en Apache?

13

Realmente agradecería algo de ayuda para comprender este comportamiento de Apache.

Me estoy comunicando a PHP desde una aplicación iPhone Objective-C en application / json. La compresión Gzip está habilitada en el servidor y la solicita el cliente.

Desde mi .htaccess:

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

Para solicitudes pequeñas, Apache está configurando el encabezado 'Content-Length'. Por ejemplo (estos valores se generan en Objective-C desde el encabezado):

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 es un encabezado que estoy agregando establecido al tamaño de la cadena JSON sin comprimir.

Como puede ver, esta solicitud es muy pequeña (217 bytes).

Aquí están los encabezados de una solicitud más grande (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;

Tenga en cuenta que Content-Length no se proporciona.

Mis preguntas:

  1. ¿Por qué Apache no envía Content-Length para la solicitud más grande?
  2. ¿El hecho de que 'Contend-Encoding = gzip' esté configurado significa que la compresión gzip todavía funciona en la solicitud más grande, aunque no puedo verificar la diferencia de tamaño?
  3. ¿Hay alguna manera de hacer que Apache incluya la longitud de contenido real para estas solicitudes más grandes para informar con mayor precisión el uso de datos a los usuarios?

Esta aplicación se puede usar en planes de datos que son caros, de ahí mi deseo de informar el uso real al usuario, no un uso inflado del 30-70% (unos pocos cientos de KB adicionales pueden no parecer mucho, pero estos planes pueden costar entre $ 1 y $ 10 por MB!).

Gracias por adelantado.

William Denniss
fuente

Respuestas:

14

Además de la respuesta de Martin Fjordvalds:

Apache usa codificación fragmentada solo si el tamaño del archivo comprimido es mayor que el DeflateBufferSize. Por lo tanto, aumentar el tamaño de este búfer evitará que el servidor use codificación fragmentada también para archivos más grandes, lo que provocará que Content-Length se envíe incluso para datos comprimidos.

Más información está disponible aquí: http://httpd.apache.org/docs/2.2/mod/mod_deflate.html#deflatebuffersize

Philippe
fuente
Buena esa. Esta es probablemente la forma más rápida de resolver este problema. Si alguien necesita un mayor nivel de personalización (p. Ej., Algunas solicitudes, no otras), consulte mi respuesta serverfault.com/a/183856/54957 para obtener una solución manual.
William Denniss
7

Parece que Apache está haciendo codificación fragmentada, esto significa que puede enviar los datos a medida que se comprimen en lugar de esperar a que se responda la respuesta completa. Es una práctica bastante estándar, aunque no estoy lo suficientemente familiarizado con Apache como para decir si se puede desactivar.

Martin Fjordvald
fuente
Gracias por la información, me apuntaste en la dirección correcta y lo resolví.
William Denniss
Aceptado. Sin embargo, para cualquiera que lea esta pregunta, lea mi respuesta para obtener una solución detallada. Básicamente, puede evitar la fragmentación (y, por lo tanto, la longitud de contenido cero) almacenando y comprimiendo la respuesta manualmente.
William Denniss
Es un poco confuso que la respuesta aceptada no sea la respuesta a la pregunta original, sino algo que lo ayudó a obtenerla. Tal vez deberías aceptar la respuesta que publicaste a continuación para aclarar las cosas un poco.
redbmk
@redbmk punto justo, simplemente no quería parecer ingrato. En realidad, Philippe tiene la solución perfecta para esto, así que he aceptado la suya sobre la mía.
William Denniss
5

OK, logré resolver esto. Como Martin F señala correctamente, Apache está fragmentando la respuesta para que no se conozca el tamaño del contenido. Para muchas personas esto es deseable (la página se carga más rápido). Esto tiene un costo de no poder informar el progreso de la descarga.

Para aquellos como yo que realmente desean informar el progreso de la descarga, si usa Apache o el soporte automático de gzip de PHP, hay poco que pueda hacer. La solución es hacerlo manualmente. Es más fácil de lo que parece:

Si está enviando archivos completos, este es un gran ejemplo en PHP para forzar un solo fragmento (con Content-Length): http://www.php.net/manual/en/function.ob-start.php # 94741

Si está enviando datos generados, use gzencode para codificar sus datos, como en el ejemplo anterior. Un requisito previo es que todos sus datos de salida se almacenen en una variable (puede usar ob_start para ayudarlo si necesita almacenar en búfer, luego obtener el contenido del búfer).

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

¡Y voilá!

Otro gran beneficio de hacerlo usted mismo es que puede establecer el nivel de compresión. Esto es excelente para mi aplicación móvil, ya que puedo establecer el nivel de compresión más alto (para que mis usuarios paguen menos por los datos), mientras que el servidor probablemente solo usa un nivel de compresión medio para una mejor compensación de CPU / tamaño. Los niveles de compresión son algo que creo que solo puede cambiar si puede editar httpd.conf (que en el alojamiento compartido, no puedo).

Así que he mantenido mi directiva .htaccess DEFLATE para todo menos para mi aplicación / json que ahora codifico de la manera anterior.

Gracias de nuevo Martin F, me diste la chispa que necesitaba para resolver esto :)

William Denniss
fuente
1
Por cierto, los ahorros con datos JSON (con claves muy repetidas) son enormes , 77% de reducción en un caso. Eso es un gran problema a $ 1 por MB ...
William Denniss
1
Probablemente deberías usar solo en strlen($replyBody)lugar de mb_strlen($replyBody, 'latin1'). La longitud del contenido es solo el número de bytes (no caracteres), que es lo que le proporciona strlen (). Usar mb_strlen () con el tipo 'latin1' funciona porque los caracteres latin1 son siempre de 8 bits, pero puede tener problemas con las codificaciones que producen bytes que no son caracteres latin1 válidos.
orden