[Ir al menú de PHP]
Funciones de compresión

Manejando ficheros comprimidos

PHP dispone de funciones que permiten manejar ficheros comprimidos utilizando para la compresión la función zlib de Jean-loup Gailly y Mark Adler.

Algunas de esas funciones son estas:

$f=gzopen(fich,mod, path)

Abre el fichero identificado por el parámetro fich y lo hace en modo especificado en el parámetro modo (lectura:r:escritura:w).

Este parámetro, además del modo de escritura debe contener un número comprendido entre cero y nueve que especifica el grado de compresión que pretende aplicarse al fichero comprimido.

El parámeto path es opcional y puede contener un valor lógico (cero ó uno). Cuando el valor de este paramétro es 1 permite incluir en el parámetro fich la ruta del directorio o subdirectorio que alberga el fichero que tratamos de abrir

Si se incluye una ruta sin especificar el valor 1 en el parámetro path aparecerá un error.

gzclose($f)

Cierra el fichero asociado al identificador de recurso $f. Esta función devuelve TRUE en caso de éxito o FALSE si se produjera un error.

gzeof($f)

Esta función devuelve 1 (TRUE) en el caso de que el puntero apunte al final del fichero abierto e identificado mediante $f.
También devuelve TRUE en caso de error.
Si el fichero estuviera abierto y el puntero apunta a una posición distinta del final del fichero devolverá FALSE.

gzseek($f,desplaza)

Desplaza -dentro del fichero identificado por $f- el puntero -a partir de su posición actual- la cantidad de bytes indicados en el parámetro desplaza

gztell($f)

Devuelve la posición actual del puntero.

gzrewind($f)

Coloca el puntero al comienzo del fichero

gzread($f, longitud)

Devuelve una cadena -descomprimida- de longitud igual a la indicada en el parámetro longitud. La lectura comienza en la posición actual del puntero y acaba cuando la longitud de la cadena leida y descomprimida sea igual al valor del parámetro longitud o cuando se haya alcanzado el final del fichero.

gzpassthru ($f)

Esta función escribe en la salida (no necesita la función echo) el contenido del fichero desde la posición actual del puntero hasta el final del fichero.

Como es lógico, si estuviera precedida de gzrewind escribiría el fichero completo.

¡¡Cuidado...!!

La función gzpassthru cierra automaticamente el fichero después de escribir su contenido. Si pones gzclose después de esta función te dará error y si quieres seguir utilizando el fichero tendrás que volver a abrirlo con la función gzopen.


gzwrite($f, cadena, long)

Esta función escribe en el fichero comprimido que se identifica por $f la cadena contenida en el parámetro cadena.

Opcionalmente puede llevar el tercer parámetro (longitud) en cuyo caso solo escribirá los primeros longitud bytes de la cadena. Si el parámetro longitud existe y es mayor que la longitud de la cadena, insertará la cadena completa.

gzputs($f, cadena, long)

Esta función es idéntica a gzwrite.

readgzfile($fichero,path)

Esta función abre de forma automática el fichero indicado como parámetro fichero, además lo lee y lo escribe de forma automática sin necesidad de usar echo ni ninguna otra función de salida.

Si el fichero no está en el mismo directorio que el script -además de incluir la ruta en la cadena fichero- es necesario añadir el segundo parámetro -path- con valor 1.

Comprimiendo cadenas

Las funciones anteriores permiten la creación, lectura y modificación de ficheros comprimidos

Sin embargo, existen otras funciones PHP que permiten comprimir cadenas. Aquí tienes algunas de ellas.

gzcompress(cadena, nivel)

Esta función devuelve una cadena comprimida a partir de una original especificada en el parámeto cadena. El nivel de compresión (valores entre 0 y 9) se especifica en el parámetro nivel.

Las cadenas resultantes de esta función pueden descomprimirse aplicando la función gzuncompress que te comento más abajo.

gzdeflate(cadena, nivel)

Se comporta de forma idéntica a la función anterior. La única salvedad parece ser que utiliza un algoritmo de compresión distinto.

Las cadenas resultantes de esta función también pueden descomprimirse aplicando la función gzdeflate.

gzencode(cad, niv, opc)

Esta función devuelve la cadena cad comprimida con el nivel especificado en niv y permite dos opciones de compresión: FORCE_GZIP ó FORCE_DEFLATE que se pueden especificarse como tercer parámetro (opc) exactamente con la sintaxis que te pongo aquí encima (sin encerrar entre comillas)

El valor por defecto (cuando no se especifica el parámetro opción) es FORCE_GZIP

¡¡Mucho cuidado...!!

No he podido encontrar -lo cual no quiere decir que no exista, que yo no lo sé- ninguna función que descomprima de forma directa las cadenas comprimidas con esta opción.

Según parece, esta función además de comprimir la cadena añade un encabezado obligatorio en los ficheros comprimidos en gzip lo cual permitiría guardar directamente la salida en un fichero normal (abierto con la función fopen y escribiendo la cadena comprimida usando la función fwrite) con extensión gz que posteriormente puede ser leido usando las funciones de lectura de ficheros comprimidos gzopen, gzread, etc. etc. ¡¡Más cuidado...!!

Lo que te comento en el párrafo anterior me funciona cuando utilizo la opción FORCE_GZIP pero con FORCE_DEFLATE no he conseguido el mismo efecto.

Hasta donde he podido saber -lo que dice el manual oficial de PHP- la diferencia entre ambas opciones estriba en que esta última no añade la suma de control crc32 y aunque me he pelado con esta función una vez más he podido comprobar que donde Dios no da... Salamanca no presta;-).

Con todo el dolor de mi corazón... tengo que decirte que.. esta opción FORCE_DEFLATE si existe, si funciona a la hora de comprimir, pero... no he sabido encontrar el procedimiento para descomprimirla eficazmente, dicho lo cual... he optado por no usarla (solución cómoda) esperando que si tú sabes de que va me envies un mensaje que contribuya a mi desasnamiento...

Otra cosa...

No he podido comprobarlo -mi hosting actualizó PHP a la versión 4.2.3 hace unos días- pero parece ser que las versiones anteriores a PHP 4.2 no incluyen el parámetro niv. Avisad@ quedas....

Descomprimiendo cadenas

gzuncompress(cadena)

Con esta función se obtiene una cadena -descomprimida- a partir de la cadena compromida indicada en el parámetro cadena- siempre que esta hubiera sido comprimida usando la función gzcompress

gzinflate(cadena)

Funciona igual que la anterior. La unica diferencia es que esta descomprime las cadenas que han sido comprimidas con gzdeflate

Funciones para buferización de salidas

ob_start()

Esta función activa la buferización (¡¡menudo palabro...!! de las salidas generadas por por el script de PHP a partir de su activación.

Dicho de otra forma, impide que las salidas generadas por el script se visualicen en la salida. A partir del momento de activar esa buferización, todas las salidas generadas se almacenan en una variable llamada ob_get_contents()

ob_end_clean()

Esta función desactiva la buferización iniciada por ob_start y borra los contenidos de la variable ob_get_contents()

ob_clean()

Esta función vacía el buffer de salida pero sin desactivar la bufferización. Las salidas posteriores a esta función seguirían siendo recogidas en el buffer.

Cabeceras para transferir información comprimida

Cuando un servidor recibe una petición de una página web el navegador del cliente siempre envía información sobre su disposición a aceptar diferentes tipos de contenidos.

Una de las informaciones que suelen enviar los navegadores suele referirse a sus disposición a aceptar contenidos codificados y esto suelen hacerlo mediante el envio de una cabecera que diría algo similar a esto: Accept-Encoding:gzip,deflate ó Accept-Encoding: gzip.

Si esta disposición es una característica común en todas las versiones modernas de los navegadores (es posible que algunas versiones antiguas no acepten esta codificación) bastará con se incluya en la respuesta (el documento transferido por el servidor al cliente) la cabecera:
Header('Content-Encoding: gzip')
para que el navegador seoa que la información llegará codificada y que debe activar -de forma automática- sus mecanismos de traducción de ese tipo de contenidos.

Algunas limitaciones

En todos estos ejemplos hemos dado por supuesto que los navegadores de los clientes aceptan la codificación gzip, pero es evidente que si eso no ocurriera la página se visualizaría erróneamente.

Si pretendes preveer esa posibilidad te sugiero que empieces tus scripts leyendo la variable
$HTTP_ACCEPT_ENCODING y a partir de su contenido establecer una bifurcación que dirija la petición bien hacia la opción comprimida -caso de que acepte la codificación- o bien hacia una opción normal en el caso de que no la acepte.

Si vas a preveer estos casos tienes que tener muy presente los riesgos que implican las cachés intermedias ya que es posible que cuando un cliente efectue la petición de una página, esa petición no llegue al servidor por haberla encontrado en un proxy y ahí puede surgir el problema de que desde el proxy se sirva la página codificada a un navegador que no acepta la codificación.

Por razones de maquetación, te comento la posible solución a este problema aquí a la derecha... ;-)


 
 

Herramientas de compresión

Existen varias herramientas para compresión de ficheros. Las más populares son las funciones de la biblioteca bzip2 de Julian Seward que generan ficheros comprimidos que se reconocen por su extensión (bz2) y la función de zlib de Jean-loup Gailly y Mark Adler para leer y grabar archivos comprimidos con extensión gz

PHP permite -siempre que estén correctamente configuradas las extensiones- trabajar con ambos compresores. No obstante, solo te comentaré mis experiencias con la librería zlib pero estoy seguro de que -si tienes interés por trabajar con bzip2- encontrarás abundante información al respecto.

Lo primero que tienes que hacer es comprobar si PHP está configurado para utilizar las funciones zlib. Si has configurado php.ini de la forma que te comenté al principio de estas «Memorias» ya tienes descomentada la extensión php_zlib.dll y si es así, al ejecutar info.php podrás ver algo similara a esto:



Si no fuera así, comprueba tu fichero php.ini y mira si la línea que dice: extension=php_zlib.dll está comentada (con un punto y coma delante) y si es así... quítale el punto y coma... ;-)

Versión 4.3.0 de PHP

Los comentarios anteriores son válidos para la versión 4.2.3 -y anteriores- de PHP. A partir de la versión 4.3.0 la librería de compresión viene incluida en el propio programa y por tanto se activa automaticamente al instalar PHP. En esta nueva versión no encontrarás la la extensión extension=php_zlib.dll en php.ini.

Las versiones evolucionan... y hay que adaptarse a los tiempos...;-)

Ejemplo de compresión y lectura de un fichero

Aquí tienes un ejemplo en el que he intentado utilizar todas la funciones de compresión de ficheros que te comenzo al margen.

Si observas las formas de apertura de los ficheros verás que son las mismas que te comenté cuando te hablaba de la gestión de ficheros.

Los modos son:

"w0" a "w9" para escritura siendo los valores de cero a nueve los indicadores de los niveles de compresión.

"r" para lectura sin indicar aquí el nivel de compresión.


<?
# asignamos un nombre al fichero con extensión "gz"
  $fichero ='prueba.gz';
# abrimos el fichero en modo escritura (w) 
# con el nivel máximo de compresión (9)
  $f=gzopen($fichero,"w9",0);
  $cadena="Este es el primer bloque de texto que hemos
               introducido en el fichero comprimido. ";
  $cadena .="Añadimos este segundo bloque";
  echo "<i>Esta es el fichero inicial:</i> ".$cadena."<br>";
# escribimos (comprimida) la cadena en el fichero
  gzwrite($f,$cadena);
# cerramos el fichero
  gzclose($f);
#abrimos el fichero en modo lectura
  $f=gzopen($fichero,"r");
  echo "<i>Estos son los tres primeros caracteres de la cadena:</i> ";
# escribimos los tres primeros caracteres, el puntero (por defecto)
# apunta al comienzo de la cadena
 echo gzread($f, 3)."<br>";
# desplazamos el puntero hasta el carácter nº 8
  gzseek($f,8);
  echo "<i>Estos son los seis caracteres siguientes al octavo:</i> ";
# escribimos seis caracteres a partir del octavo
  echo gzread($f, 6)."<br>";
  echo "<i>Ahora el puntero está en:</i> ";
# buscamos la posición actual de puntero
  echo gztell($f)."<br>";
# movemos el puntero hasta el comienzo del fichero
    gzrewind($f);
echo "<i>Estos son los diez primeros caracteres de la cadena:</i> ";
# escribimos los diez primeros caracteres del fichero
  echo gzread($f, 10)."<br>";
# volvemos el puntero al comienzo del fichero
    gzrewind($f);
  echo "<i>Escribimos el fichero completo:</i> ";
# con gzpasthru escribimos el fichero completo
# el puntero está al principio porque alli lo ha situado gzrewind
# no necesitamos utilizar "echo"  ni "print" ya que gzpassthru
# escribe directamente el contenido del fichero
  gzpassthru($f);
# tenemos que volver a abrir el fichero ya que gzpassthru
# se encargó de cerrarlo después de leerlo
 $f=gzopen($fichero,"r");
  echo "<br><i>Aquí estará todo el fichero:</i> ";
  gzpassthru ($f);
# la función readgzfile abre el fichero, imprime su contenido y lo cierra
  echo "<br><i>Aqui se imprime la cadena 
                         completa usando readgzfile</i>: <br>";
  readgzfile($fichero);
# con gzfile también se abre el fichero, 
# pero ahora el contenido no se presenta
# directamente. Es recogido en un array. 
# Para visualizarlo debemos imprimir
# el primer elemento del array. 
  $z=gzfile($fichero);
  echo "<br><i>Este es el primer elemento (0) 
                    del array generado por gzfile</i>: ".$z[0];
# gzfile cierra el fichero.
# No podemos poner gzclose porque nos daría error 
 ?>


comprime1.php

Utilizando un directorio distinto

El ejemplo anterior está desarrollado para el supuesto que el script y el fichero comprimido estén en el mismo directorio.

Si quieres utilizar estas funciones utilizando ficheros alojados en un directorio distinto, solo tendrás que recordar que algunas funciones deben incluir el parámetro complementario 1. Estos son las modificaciones que deberías efectuar:


¡¡Ten cuidado con este detalle... y evitarás algunos de los sudores que yo he tenido que pasar... ;-)

Elección del grado óptimo de compresión

Puede parecer -a primera vista- que la condición óptima de compresión sería elegir el nivel 9 y eso es cierto si tomamos únicamente en consideración el tamaño final del fichero comprimido, pero... siempre hay un pero...;-) esa opción implica una raletización del proceso de compresión ya que obliga a ejecutar el algoritmo de compresión reiteradas veces y es probable que la reducción real del tamaño no justifique ese tiempo de compresión.

Sin que pueda considerarse ninguna referencia exacta -la compresión alcanzable depende del contenido del fichero y en consecuencia no puede establecerse una relación funcional entre reducción de tamaño/nivel de compresión- he hecho un experimento comprimiendo los contenidos de dos de mis páginas (elegidas aleatoriamente) y estos han sido los resultados.

Si observas mis resultados podrás comprobar como -aparentemente- a partir del grado 2 la reducción de tamaño del fichero es ínfima. Ahora bien, es posible que con otros contenidos esta relación se modifique sustancialmente... no lo se, pero lo que si parece claro es que no hay una relación lineal entre el tamaño final resultante y el nivel de compresión.

Compresión de cadenas

Aquí tienes un ejemplo en el que uso las tres funciones de compresión de cadenas así como las opciones de descompresión y lectura de cada una de ellas.


<?
# creamos una cadena de ejemplo

  $cadena="Esta es la cadena a comprimir. Intentaremos que sea larga 
    porque parece que si la hacemos muy corta en vez de reducirse
    su tamaño parece que aumenta. Y como sigue siendo enormemente
    grande la cadena comprimida intentaremos hacerla aun mayor 
    a ver que pasa ";
  
# comprimimos con la función gzcompress
  $c=gzcompress($cadena,9);
     echo "<br>".$c;

# descomprimimos con la función gzcompress
  $dc=gzuncompress($c);
     echo "<br>".$dc."<br>";

# ahora utilizamos la función gzencode
  $c1=gzencode($cadena,9,FORCE_GZIP);
  echo "<br>".$c1."<br>";

# el resultado lo guardamos en un fichero con extensión gz
# pero abierto en modo "normal", es decir escribiendo
# dentro del fichero la cadena "tal cual" fue devuelta
# por gzencode

  $f=fopen("pepe.gz","w");
  fwrite($f,$c1);
  fclose($f);
# abrimos el fichero anterior utilizando las funciones
# de lectura de fichero comprimidos

  $f=gzopen("pepe.gz","r");
  readgzfile("pepe.gz");
  gzclose($f);
# borramos el fichero una vez leido
  unlink("pepe.gz");

# otra opción de compresión de cadenas utilizando la función
# gzdeflate 
  $c2= gzdeflate($cadena,9);
    echo "<br><BR>".$c2;
# con la función gzinflate podemos descomprimir la cadena
# comprimida generada por gzdeflate

	 $dc2=gzinflate($c2);
     echo "<br>".$dc2;
 
 ?>


comprime3.php

Economizando espacio en el servidor

Aunque no pueden esperarse milagros si parece que es posible alguna economía de espacio almacenando ficheros comprimidos en el servidor y utilizando un script de descompresión a la hora de visualizar la página.

Aquí tienes un ejemplo con dos scripts

Este primero, efectua la compresión de una página web cuyo tamaño original es de 23.496 bytes. El fichero comprimido resultante ocupa 7.660 bytes. Como verás, el fichero se reduce a poco más del 30% del original.


<?
# Iniciamos una variable "vacia"
$cadena="";
# Abrimos el fichero en modo lectura (r)
$f1=fopen("tramo4_1.html","r");
# hacemos un bucle para leer el fichero
# hasta encontrar el final (feof) y vamos recogiendo
# el contenido en la variable
while (!feof($f1)) {
    $cadena .= fgets($f1, 1024);
  }
# comprimimos la cadena con gzencode
# con lo cual la propia función añade los "encabezados"
# de formato gzip
$c1=gzencode($cadena,3,FORCE_GZIP);
# abrimos un nuevo fichero modo escritura (w)
con "fopen", es decir como un fichero normal
 $f=fopen("tramo4_1.html.gz","w");
# escribimos la cadena "tal cual"
# en este fichero
  fwrite($f,$c1);
# cerramos el fichero comprimido
  fclose($f);
  echo "La compresión ha terminado";
?>

Una vez efectuada la compresión anterior, ya podríamos borrar el fichero original y dejar unicamente el comprimido en el servidor. Estaríamos ahorrando casi un 70% de espacio... aunque... no quiero convertirme en vendedor de crecepelos milagrosos...;-) así que mi sentido ético me obliga a recordarte que esta buena ratio de compresión solo la conseguirías con documentos de este tipo -html- pero claro... como lo que en realidad ocupa mucho espacio son las imágenes, los sonidos, etc. y en esos practicamente no se puede comprimir nada, pues... a la hora de la verdad el ahorro de espacio puede terminar siendo... el chocolate del loro... pero...

Bueno... chocolates aparte.. aquí tienes el script que permitiría visualizar en el navegador del cliente el contenido del fichero comprimido


<?
# abrimos el fichero comprimido con "gzopen"
 $f=gzopen("tramo4_1.html.gz","r");
 # leemos el contenido completo
 # en forma transparente ya que readgzfile descomprime
 # la salida
  readgzfile("tramo4_1.html.gz");
  # cerramos el fichero
  gzclose($f);
?>


Ejecutar script

Economizando tiempo de transferencia

No solo se puede economizar espacio en el servidor. También es posible enviar comprimidas -desde el servidor hasta el cliente- las páginas web

En ese caso, será el propio navegador el que interprete la información comprimida y la presente de una manera transparente. Lo que habremos ahorrado habrá sido tiempo de transferencia, pero igual que ocurría en el comentario anterior... esa reducción del volumen de información a transferir afecta unicamente al contenido de la página pero... si como ocurre en este ejemplo, la página contiene muchas imágenes, etc. etc. es posible que los resultados no sean demasiado sorprendentes ya que la página en sí no representa un porcentaje demasiado alto del volumen total de información a negociar, pero... menos da una piedra... algo es algo.... ;-).

Aquí tienes un ejemplo de como enviar comprimido el contenido de una página web...


<?
# activamos la buferización de la salida
# para que no se presenten los resultados del script
#directamente en la página
# ¡¡Cuidado con no dejar lineas en blanco delante del script
# ya que vamos a insertar luego Headers!!
ob_start();
# abrimos y leemos el fichero html
$f1=fopen("tramo4_1.html","r");
fpassthru($f1);
# recogemos el contenido del buffer
# en la variable cadena
$cadena = ob_get_contents();
# comprimimos la cadea con gzencode
# para que incluya los encabezados "gzip"
 $cd=gzencode($cadena,3,FORCE_GZIP);
 # desactivamos la "buferización"
 # y borramos el contenido del buffer
ob_end_clean();
 # insertamos la cabeceras
 # indicando el tipo de contenido
	Header('Content-Encoding: gzip');
	Header('Content-Length: ' . strlen($cd));
# presentamos el contenido (cadena comprimida) que será
# "traducido" automáticamente por el navegador
 echo $cd;
?>


Ejecutar script

Economizando espacio y tiempo

Este que te inserto aquí, es un ejemplo -muy similar al anterior- en el que se recoge la información a partir de un fichero comprimido -economía de espacio- y se envía al cliente también comprimida -economía de tiempo- como ves... a este paso... voy a dar la impresión de ser un aspirante a la cartera ministerial de economía... pero... te juro que... no es esa mi intención... palabra... ;-)


<?
ob_start();
#En este caso abrimos el fichero con "gzopen"
# ya que se trata de un fichero comprimido
# todo lo demás es idéntico al ejemplo anterior
$f1=gzopen("tramo4_1.html.gz","r");
gzpassthru($f1);
$cadena = ob_get_contents();
 $cd=gzencode($cadena,3,FORCE_GZIP);
 ob_end_clean();
	Header('Content-Encoding: gzip');
	Header('Content-Length: ' . strlen($cd));
 echo $cd;
?>


Ejecutar script

Evitando problemas con las cachés intermedias

Si optas por discriminar entre navegadores que aceptan/no aceptan codificación podrás evitar los riesgos de utilización de una versión incorrecta de la página almacenada en una caché intemedia entre servidor y cliente incluyendo estas cabeceras:
     header ("Cache-Control: no-store, no-cache, must-revalidate");
     header ("Pragma: no-cache");


que impedirían el almacenamiento en caché de la página y obligarían a hacer el requerimiento directamente al servidor.

Una opción alternativa a la anterior sería insertar la cabecera:

     header ("Vary: Accept-Encoding");
Con esta cabecera se informa al cliente del tipo de información que se está sirviendo (en este caso indicaría que se sirve codificado) y si el navegador del cliente aceptara ese formato podría servirse desde la caché intermedia, pero en caso contrario lo interpretaría como no caché y haría la petición al servidor.


Sugerir a un/a amig@ Envíame tus comentarios
Anterior
Indice
Siguiente